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); const asset_object& backing_backing = backing_bitasset_data.options.short_backing_asset(d);
FC_ASSERT( !backing_backing.is_market_issued(), FC_ASSERT( !backing_backing.is_market_issued(),
"May not create a bitasset backed by a bitasset backed by a bitasset." ); "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 && FC_ASSERT( op.bitasset_options->feed_lifetime_sec > chain_parameters.block_interval &&
op.bitasset_options->force_settlement_delay_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 { { try {
database& d = db(); database& d = db();
if( o.new_issuer ) FC_ASSERT(d.find_object(*o.new_issuer));
const asset_object& a = o.asset_to_update(d); const asset_object& a = o.asset_to_update(d);
auto a_copy = a; auto a_copy = a;
a_copy.options = o.new_options; a_copy.options = o.new_options;
a_copy.validate(); 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. //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), FC_ASSERT(!(o.new_options.issuer_permissions & ~a.options.issuer_permissions),
"Cannot reinstate previously revoked issuer permissions on an asset."); "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(a.dynamic_asset_data_id(d).current_supply == 0);
FC_ASSERT(d.find_object(o.new_options.short_backing_asset)); 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; bitasset_to_update = &b;

View file

@ -16,6 +16,8 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#include <graphene/chain/asset_object.hpp> #include <graphene/chain/asset_object.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/limit_order_object.hpp>
#include <fc/uint128.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 // 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;
} }
/** /**
@ -316,7 +358,7 @@ bool database::fill_order(const force_settlement_object& settle, const asset& pa
* *
* @return true if a margin call was executed. * @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 { { try {
if( !mia.is_market_issued() ) return false; if( !mia.is_market_issued() ) return false;
const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this); 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 ); 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 // stop when limit orders are selling too little USD for too much CORE
auto min_price = bitasset.current_feed.max_short_squeeze_price(); 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 ); 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 // NOTE limit_price_index is sorted from greatest to least
auto limit_itr = limit_price_index.lower_bound( max_price ); 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_end ) {
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" );
return false; 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(); usd_for_sale = limit_itr->amount_for_sale();
} }
else return filled_limit; else return filled_limit;
// wdump((match_price));
// edump((usd_for_sale));
match_price.validate(); match_price.validate();
// wdump((match_price)(~call_itr->call_price) );
if( match_price > ~call_itr->call_price ) if( match_price > ~call_itr->call_price )
{ {
return filled_limit; return filled_limit;
} }
auto usd_to_buy = call_itr->get_debt(); auto usd_to_buy = call_itr->get_debt();
// edump((usd_to_buy));
if( usd_to_buy * match_price > call_itr->get_collateral() ) if( usd_to_buy * match_price > call_itr->get_collateral() )
{ {
FC_ASSERT( enable_black_swan ); 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; return true;
} }
asset call_pays, call_receives, order_pays, order_receives; asset call_pays, call_receives, order_pays, order_receives;
if( usd_to_buy >= usd_for_sale ) if( usd_to_buy >= usd_for_sale )
{ // fill order { // fill order
//ilog( "filling all of limit order" );
call_receives = usd_for_sale; call_receives = usd_for_sale;
order_receives = usd_for_sale * match_price; order_receives = usd_for_sale * match_price;
call_pays = order_receives; 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_limit = true;
filled_call = (usd_to_buy == usd_for_sale); filled_call = (usd_to_buy == usd_for_sale);
} } else { // fill call
else // fill call
{
call_receives = usd_to_buy; call_receives = usd_to_buy;
order_receives = usd_to_buy * match_price; order_receives = usd_to_buy * match_price;
call_pays = order_receives; 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; auto old_call_itr = call_itr;
if( filled_call ) ++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; 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 } // whlie call_itr != call_end
return filled_limit; return filled_limit;
@ -454,16 +459,6 @@ void database::pay_order( const account_object& receiver, const asset& receives,
adjust_balance(receiver.get_id(), 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 ) asset database::calculate_market_fee( const asset_object& trade_asset, const asset& trade_amount )
{ {
assert( trade_asset.id == trade_amount.asset_id ); assert( trade_asset.id == trade_amount.asset_id );

View file

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

View file

@ -92,6 +92,7 @@
#define GRAPHENE_DEFAULT_BURN_PERCENT_OF_FEE (20*GRAPHENE_1_PERCENT) #define GRAPHENE_DEFAULT_BURN_PERCENT_OF_FEE (20*GRAPHENE_1_PERCENT)
#define GRAPHENE_WITNESS_PAY_PERCENT_PRECISION (1000000000) #define GRAPHENE_WITNESS_PAY_PERCENT_PRECISION (1000000000)
#define GRAPHENE_DEFAULT_MAX_ASSERT_OPCODE 1 #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_GENESIS_TIMESTAMP (1431700000) /// Should be divisible by GRAPHENE_DEFAULT_BLOCK_INTERVAL
#define GRAPHENE_MAX_WORKER_NAME_LENGTH 63 #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 force_settlement_object& order, bool create_virtual_op = true);
void cancel_order(const limit_order_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, * Matches the two orders,
* *
@ -381,7 +391,6 @@ namespace graphene { namespace chain {
// helpers to fill_order // helpers to fill_order
void pay_order( const account_object& receiver, const asset& receives, const asset& pays ); 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 calculate_market_fee(const asset_object& recv_asset, const asset& trade_amount);
asset pay_market_fees( const asset_object& recv_asset, const asset& receives ); asset pay_market_fees( const asset_object& recv_asset, const asset& receives );

View file

@ -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 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) 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 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 void validate()const
{ {
@ -614,6 +615,7 @@ FC_REFLECT( graphene::chain::chain_parameters,
(witness_pay_per_block) (witness_pay_per_block)
(worker_budget_per_day) (worker_budget_per_day)
(max_predicate_opcode) (max_predicate_opcode)
(fee_liquidation_threshold)
) )
FC_REFLECT_TYPENAME( graphene::chain::share_type ) FC_REFLECT_TYPENAME( graphene::chain::share_type )

View file

@ -21,7 +21,7 @@
#include <fc/uint128.hpp> #include <fc/uint128.hpp>
namespace graphene { namespace chain { 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 { { try {
database& d = db(); 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); _receive_asset = &op.min_to_receive.asset_id(d);
if( _sell_asset->options.whitelist_markets.size() ) 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() ) 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( _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 ) ); 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(); return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) } } 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 { { try {
const auto& seller_stats = _seller->statistics(db()); 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() ) if( op.amount_to_sell.asset_id == asset_id_type() )
{ {
bal.total_core_in_orders += op.amount_to_sell.amount; 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); 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.seller = _seller->id;
obj.for_sale = op.amount_to_sell.amount; obj.for_sale = op.amount_to_sell.amount;
obj.sell_price = op.get_price(); obj.sell_price = op.get_price();
obj.expiration = op.expiration; 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: FC_ASSERT( !op.fill_or_kill || filled );
// - 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;
const auto& limit_order_idx = db().get_index_type<limit_order_index>(); return order_id;
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;
} FC_CAPTURE_AND_RETHROW( (op) ) } } 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 { { try {
database& d = db(); database& d = db();
@ -130,7 +81,7 @@ void_result limit_order_cancel_evaluator::do_evaluate( const limit_order_cancel_
return void_result(); return void_result();
} FC_CAPTURE_AND_RETHROW( (o) ) } } 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 { { try {
database& d = db(); 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 quote_asset = _order->sell_price.quote.asset_id;
auto refunded = _order->amount_for_sale(); 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. // 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? // Do I need to check calls in both assets?