2015-06-08 15:50:35 +00:00
|
|
|
/*
|
2015-10-12 17:48:40 +00:00
|
|
|
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
|
|
|
|
* All rights reserved.
|
|
|
|
|
*
|
|
|
|
|
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
|
|
|
*
|
|
|
|
|
* 1. Any modified source or binaries are used only with the BitShares network.
|
|
|
|
|
*
|
|
|
|
|
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
|
|
|
*
|
|
|
|
|
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
2015-06-08 15:50:35 +00:00
|
|
|
*
|
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
|
|
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
|
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
|
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
|
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
|
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
2015-10-12 17:02:59 +00:00
|
|
|
*
|
2015-06-08 15:50:35 +00:00
|
|
|
*/
|
2015-07-08 20:39:23 +00:00
|
|
|
#include <graphene/chain/market_evaluator.hpp>
|
2015-06-08 15:50:35 +00:00
|
|
|
#include <graphene/chain/account_object.hpp>
|
2015-07-07 21:39:38 +00:00
|
|
|
#include <graphene/chain/exceptions.hpp>
|
2015-10-23 22:13:33 +00:00
|
|
|
#include <graphene/chain/hardfork.hpp>
|
|
|
|
|
#include <fc/smart_ref_impl.hpp>
|
|
|
|
|
#include <graphene/chain/protocol/fee_schedule.hpp>
|
2015-06-08 15:50:35 +00:00
|
|
|
#include <fc/uint128.hpp>
|
|
|
|
|
|
|
|
|
|
namespace graphene { namespace chain {
|
2015-07-08 20:39:23 +00:00
|
|
|
void_result limit_order_create_evaluator::do_evaluate(const limit_order_create_operation& op)
|
|
|
|
|
{ try {
|
2015-10-30 17:10:09 +00:00
|
|
|
const database& d = db();
|
2015-07-08 20:39:23 +00:00
|
|
|
|
|
|
|
|
FC_ASSERT( op.expiration >= d.head_block_time() );
|
|
|
|
|
|
|
|
|
|
_seller = this->fee_paying_account;
|
|
|
|
|
_sell_asset = &op.amount_to_sell.asset_id(d);
|
|
|
|
|
_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() );
|
|
|
|
|
if( _sell_asset->options.blacklist_markets.size() )
|
|
|
|
|
FC_ASSERT( _sell_asset->options.blacklist_markets.find(_receive_asset->id) == _sell_asset->options.blacklist_markets.end() );
|
|
|
|
|
|
2015-10-30 17:47:22 +00:00
|
|
|
if( d.head_block_time() <= HARDFORK_416_TIME )
|
|
|
|
|
{
|
|
|
|
|
if( _sell_asset->options.flags & white_list ) FC_ASSERT( _seller->is_authorized_asset( *_sell_asset, d ) );
|
|
|
|
|
if( _receive_asset->options.flags & white_list ) FC_ASSERT( _seller->is_authorized_asset( *_receive_asset, d ) );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
FC_ASSERT( _seller->is_authorized_asset( *_sell_asset, d ) );
|
|
|
|
|
FC_ASSERT( _seller->is_authorized_asset( *_receive_asset, d ) );
|
|
|
|
|
}
|
2015-07-08 20:39:23 +00:00
|
|
|
|
|
|
|
|
FC_ASSERT( d.get_balance( *_seller, *_sell_asset ) >= op.amount_to_sell, "insufficient balance",
|
|
|
|
|
("balance",d.get_balance(*_seller,*_sell_asset))("amount_to_sell",op.amount_to_sell) );
|
|
|
|
|
|
|
|
|
|
return void_result();
|
|
|
|
|
} FC_CAPTURE_AND_RETHROW( (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) {
|
|
|
|
|
if( op.amount_to_sell.asset_id == asset_id_type() )
|
|
|
|
|
{
|
|
|
|
|
bal.total_core_in_orders += op.amount_to_sell.amount;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
db().adjust_balance(op.seller, -op.amount_to_sell);
|
|
|
|
|
|
|
|
|
|
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 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);
|
|
|
|
|
|
|
|
|
|
FC_ASSERT( !op.fill_or_kill || filled );
|
|
|
|
|
|
|
|
|
|
return order_id;
|
|
|
|
|
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
|
|
|
|
|
|
|
|
|
void_result limit_order_cancel_evaluator::do_evaluate(const limit_order_cancel_operation& o)
|
|
|
|
|
{ try {
|
|
|
|
|
database& d = db();
|
|
|
|
|
|
|
|
|
|
_order = &o.order(d);
|
|
|
|
|
FC_ASSERT( _order->seller == o.fee_paying_account );
|
|
|
|
|
|
|
|
|
|
return void_result();
|
|
|
|
|
} FC_CAPTURE_AND_RETHROW( (o) ) }
|
|
|
|
|
|
|
|
|
|
asset limit_order_cancel_evaluator::do_apply(const limit_order_cancel_operation& o)
|
|
|
|
|
{ try {
|
|
|
|
|
database& d = db();
|
|
|
|
|
|
|
|
|
|
auto base_asset = _order->sell_price.base.asset_id;
|
|
|
|
|
auto quote_asset = _order->sell_price.quote.asset_id;
|
|
|
|
|
auto refunded = _order->amount_for_sale();
|
|
|
|
|
|
2015-10-27 15:05:59 +00:00
|
|
|
/// TODO... implement this refund in a better way
|
2015-10-23 17:26:11 +00:00
|
|
|
if( true ) // HARD FORK d.head_block_time() >
|
|
|
|
|
{
|
|
|
|
|
const auto& fees = d.current_fee_schedule();
|
|
|
|
|
asset create_fee = fees.calculate_fee( limit_order_create_operation() );
|
|
|
|
|
|
|
|
|
|
// then the create fee was only the network fee and not the
|
|
|
|
|
// full create_fee
|
|
|
|
|
const auto& gprops = d.get_global_properties();
|
|
|
|
|
auto cashback_percent = GRAPHENE_100_PERCENT - gprops.parameters.network_percent_of_fee;
|
|
|
|
|
auto cashback_amount = (create_fee.amount * cashback_percent) / GRAPHENE_100_PERCENT;
|
|
|
|
|
create_fee.amount -= cashback_amount;
|
|
|
|
|
|
|
|
|
|
const auto& core_asset_data = asset_id_type(0)(d).dynamic_asset_data_id(d);
|
|
|
|
|
d.modify( core_asset_data, [&]( asset_dynamic_data_object& addo ) {
|
|
|
|
|
addo.accumulated_fees -= create_fee.amount;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
d.adjust_balance( o.fee_paying_account, create_fee );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
d.cancel_order(*_order, false /* don't create a virtual op*/);
|
2015-07-08 20:39:23 +00:00
|
|
|
|
|
|
|
|
// 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?
|
2015-10-23 17:26:11 +00:00
|
|
|
d.check_call_orders(base_asset(d));
|
|
|
|
|
d.check_call_orders(quote_asset(d));
|
2015-07-08 20:39:23 +00:00
|
|
|
|
|
|
|
|
return refunded;
|
|
|
|
|
} FC_CAPTURE_AND_RETHROW( (o) ) }
|
2015-06-08 15:50:35 +00:00
|
|
|
|
2015-06-18 19:17:48 +00:00
|
|
|
void_result call_order_update_evaluator::do_evaluate(const call_order_update_operation& o)
|
2015-06-08 15:50:35 +00:00
|
|
|
{ try {
|
|
|
|
|
database& d = db();
|
|
|
|
|
|
|
|
|
|
_paying_account = &o.funding_account(d);
|
2015-06-18 19:17:48 +00:00
|
|
|
_debt_asset = &o.delta_debt.asset_id(d);
|
2015-06-16 13:52:33 +00:00
|
|
|
FC_ASSERT( _debt_asset->is_market_issued(), "Unable to cover ${sym} as it is not a collateralized asset.",
|
2015-06-08 15:50:35 +00:00
|
|
|
("sym", _debt_asset->symbol) );
|
2015-06-18 19:17:48 +00:00
|
|
|
|
|
|
|
|
_bitasset_data = &_debt_asset->bitasset_data(d);
|
|
|
|
|
|
2015-06-19 13:57:23 +00:00
|
|
|
/// if there is a settlement for this asset, then no further margin positions may be taken and
|
|
|
|
|
/// all existing margin positions should have been closed va database::globally_settle_asset
|
|
|
|
|
FC_ASSERT( !_bitasset_data->has_settlement() );
|
|
|
|
|
|
2015-06-18 19:17:48 +00:00
|
|
|
FC_ASSERT( o.delta_collateral.asset_id == _bitasset_data->options.short_backing_asset );
|
2015-06-08 15:50:35 +00:00
|
|
|
|
2015-06-16 13:52:33 +00:00
|
|
|
if( _bitasset_data->is_prediction_market )
|
2015-06-18 19:17:48 +00:00
|
|
|
FC_ASSERT( o.delta_collateral.amount == o.delta_debt.amount );
|
2015-07-07 21:39:38 +00:00
|
|
|
else if( _bitasset_data->current_feed.settlement_price.is_null() )
|
|
|
|
|
FC_THROW_EXCEPTION(insufficient_feeds, "Cannot borrow asset with no price feed.");
|
2015-06-08 15:50:35 +00:00
|
|
|
|
2015-06-18 19:17:48 +00:00
|
|
|
if( o.delta_debt.amount < 0 )
|
2015-06-08 15:50:35 +00:00
|
|
|
{
|
2015-06-18 19:17:48 +00:00
|
|
|
FC_ASSERT( d.get_balance(*_paying_account, *_debt_asset) >= o.delta_debt,
|
2015-06-16 13:52:33 +00:00
|
|
|
"Cannot cover by ${c} when payer only has ${b}",
|
2015-06-18 19:17:48 +00:00
|
|
|
("c", o.delta_debt.amount)("b", d.get_balance(*_paying_account, *_debt_asset).amount) );
|
2015-06-08 15:50:35 +00:00
|
|
|
}
|
2015-06-18 19:17:48 +00:00
|
|
|
|
|
|
|
|
if( o.delta_collateral.amount > 0 )
|
2015-06-16 13:52:33 +00:00
|
|
|
{
|
2015-06-18 19:17:48 +00:00
|
|
|
FC_ASSERT( d.get_balance(*_paying_account, _bitasset_data->options.short_backing_asset(d)) >= o.delta_collateral,
|
|
|
|
|
"Cannot increase collateral by ${c} when payer only has ${b}", ("c", o.delta_collateral.amount)
|
|
|
|
|
("b", d.get_balance(*_paying_account, o.delta_collateral.asset_id(d)).amount) );
|
2015-06-16 13:52:33 +00:00
|
|
|
}
|
|
|
|
|
|
2015-06-18 19:17:48 +00:00
|
|
|
return void_result();
|
2015-06-08 15:50:35 +00:00
|
|
|
} FC_CAPTURE_AND_RETHROW( (o) ) }
|
|
|
|
|
|
2015-06-16 13:52:33 +00:00
|
|
|
|
2015-06-18 19:17:48 +00:00
|
|
|
void_result call_order_update_evaluator::do_apply(const call_order_update_operation& o)
|
2015-06-19 19:57:08 +00:00
|
|
|
{ try {
|
2015-06-08 15:50:35 +00:00
|
|
|
database& d = db();
|
|
|
|
|
|
2015-06-18 19:17:48 +00:00
|
|
|
if( o.delta_debt.amount != 0 )
|
2015-06-16 13:52:33 +00:00
|
|
|
{
|
2015-06-18 19:17:48 +00:00
|
|
|
d.adjust_balance( o.funding_account, o.delta_debt );
|
|
|
|
|
|
|
|
|
|
// Deduct the debt paid from the total supply of the debt asset.
|
2015-06-16 13:52:33 +00:00
|
|
|
d.modify(_debt_asset->dynamic_asset_data_id(d), [&](asset_dynamic_data_object& dynamic_asset) {
|
2015-06-18 19:17:48 +00:00
|
|
|
dynamic_asset.current_supply += o.delta_debt.amount;
|
2015-06-16 13:52:33 +00:00
|
|
|
assert(dynamic_asset.current_supply >= 0);
|
|
|
|
|
});
|
|
|
|
|
}
|
2015-06-08 15:50:35 +00:00
|
|
|
|
2015-06-18 19:17:48 +00:00
|
|
|
if( o.delta_collateral.amount != 0 )
|
|
|
|
|
{
|
|
|
|
|
d.adjust_balance( o.funding_account, -o.delta_collateral );
|
|
|
|
|
|
|
|
|
|
// Adjust the total core in orders accodingly
|
|
|
|
|
if( o.delta_collateral.asset_id == asset_id_type() )
|
|
|
|
|
{
|
|
|
|
|
d.modify(_paying_account->statistics(d), [&](account_statistics_object& stats) {
|
|
|
|
|
stats.total_core_in_orders += o.delta_collateral.amount;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-06-16 13:52:33 +00:00
|
|
|
auto& call_idx = d.get_index_type<call_order_index>().indices().get<by_account>();
|
2015-06-18 19:17:48 +00:00
|
|
|
auto itr = call_idx.find( boost::make_tuple(o.funding_account, o.delta_debt.asset_id) );
|
|
|
|
|
const call_order_object* call_obj = nullptr;
|
2015-06-19 19:57:08 +00:00
|
|
|
|
2015-06-16 13:52:33 +00:00
|
|
|
if( itr == call_idx.end() )
|
|
|
|
|
{
|
2015-06-18 19:17:48 +00:00
|
|
|
FC_ASSERT( o.delta_collateral.amount > 0 );
|
|
|
|
|
FC_ASSERT( o.delta_debt.amount > 0 );
|
|
|
|
|
|
|
|
|
|
call_obj = &d.create<call_order_object>( [&](call_order_object& call ){
|
2015-06-19 22:14:23 +00:00
|
|
|
call.borrower = o.funding_account;
|
|
|
|
|
call.collateral = o.delta_collateral.amount;
|
|
|
|
|
call.debt = o.delta_debt.amount;
|
2015-06-21 23:37:14 +00:00
|
|
|
call.call_price = price::call_price(o.delta_debt, o.delta_collateral,
|
2015-06-19 22:14:23 +00:00
|
|
|
_bitasset_data->current_feed.maintenance_collateral_ratio);
|
2015-09-30 22:30:44 +00:00
|
|
|
|
2015-06-19 22:14:23 +00:00
|
|
|
});
|
2015-06-16 13:52:33 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-06-18 19:17:48 +00:00
|
|
|
call_obj = &*itr;
|
|
|
|
|
|
|
|
|
|
d.modify( *call_obj, [&]( call_order_object& call ){
|
|
|
|
|
call.collateral += o.delta_collateral.amount;
|
|
|
|
|
call.debt += o.delta_debt.amount;
|
2015-06-22 19:04:19 +00:00
|
|
|
if( call.debt > 0 )
|
|
|
|
|
{
|
|
|
|
|
call.call_price = price::call_price(call.get_debt(), call.get_collateral(),
|
|
|
|
|
_bitasset_data->current_feed.maintenance_collateral_ratio);
|
|
|
|
|
}
|
2015-06-18 19:17:48 +00:00
|
|
|
});
|
2015-06-16 13:52:33 +00:00
|
|
|
}
|
2015-06-18 19:17:48 +00:00
|
|
|
|
|
|
|
|
auto debt = call_obj->get_debt();
|
2015-06-19 22:14:23 +00:00
|
|
|
if( debt.amount == 0 )
|
2015-06-08 15:50:35 +00:00
|
|
|
{
|
2015-06-18 19:17:48 +00:00
|
|
|
FC_ASSERT( call_obj->collateral == 0 );
|
|
|
|
|
d.remove( *call_obj );
|
|
|
|
|
return void_result();
|
2015-06-08 15:50:35 +00:00
|
|
|
}
|
2015-06-18 19:17:48 +00:00
|
|
|
|
2015-06-19 22:14:23 +00:00
|
|
|
FC_ASSERT(call_obj->collateral > 0 && call_obj->debt > 0);
|
|
|
|
|
|
|
|
|
|
// then we must check for margin calls and other issues
|
2015-06-19 19:57:08 +00:00
|
|
|
if( !_bitasset_data->is_prediction_market )
|
|
|
|
|
{
|
2015-10-08 19:03:35 +00:00
|
|
|
call_order_id_type call_order_id = call_obj->id;
|
2015-06-18 19:17:48 +00:00
|
|
|
|
2015-06-19 22:14:23 +00:00
|
|
|
// check to see if the order needs to be margin called now, but don't allow black swans and require there to be
|
2015-06-19 19:57:08 +00:00
|
|
|
// limit orders available that could be used to fill the order.
|
|
|
|
|
if( d.check_call_orders( *_debt_asset, false ) )
|
|
|
|
|
{
|
2015-10-08 19:03:35 +00:00
|
|
|
const auto call_obj = d.find(call_order_id);
|
2015-07-16 19:24:00 +00:00
|
|
|
// if we filled at least one call order, we are OK if we totally filled.
|
|
|
|
|
GRAPHENE_ASSERT(
|
2015-10-08 19:11:32 +00:00
|
|
|
!call_obj,
|
2015-07-16 19:24:00 +00:00
|
|
|
call_order_update_unfilled_margin_call,
|
|
|
|
|
"Updating call order would trigger a margin call that cannot be fully filled",
|
2015-10-08 19:11:32 +00:00
|
|
|
("a", ~call_obj->call_price )("b", _bitasset_data->current_feed.settlement_price)
|
2015-07-16 19:24:00 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-10-08 19:03:35 +00:00
|
|
|
const auto call_obj = d.find(call_order_id);
|
2015-10-08 19:11:32 +00:00
|
|
|
FC_ASSERT( call_obj, "no margin call was executed and yet the call object was deleted" );
|
2015-09-30 22:30:44 +00:00
|
|
|
//edump( (~call_obj->call_price) ("<")( _bitasset_data->current_feed.settlement_price) );
|
2015-07-16 19:24:00 +00:00
|
|
|
// We didn't fill any call orders. This may be because we
|
|
|
|
|
// aren't in margin call territory, or it may be because there
|
|
|
|
|
// were no matching orders. In the latter case, we throw.
|
|
|
|
|
GRAPHENE_ASSERT(
|
|
|
|
|
~call_obj->call_price < _bitasset_data->current_feed.settlement_price,
|
|
|
|
|
call_order_update_unfilled_margin_call,
|
|
|
|
|
"Updating call order would trigger a margin call that cannot be fully filled",
|
2015-10-08 19:11:32 +00:00
|
|
|
("a", ~call_obj->call_price )("b", _bitasset_data->current_feed.settlement_price)
|
2015-07-16 19:24:00 +00:00
|
|
|
);
|
2015-06-19 19:57:08 +00:00
|
|
|
}
|
2015-06-18 19:17:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return void_result();
|
2015-06-19 19:57:08 +00:00
|
|
|
} FC_CAPTURE_AND_RETHROW( (o) ) }
|
2015-06-08 15:50:35 +00:00
|
|
|
|
|
|
|
|
} } // graphene::chain
|