Compare commits
18 commits
master
...
feature/po
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fce9f37e9c | ||
|
|
14a3e7d9e9 | ||
|
|
93c4793d0a | ||
|
|
dcc4c77989 | ||
|
|
6c9cc5d97b | ||
|
|
1510197625 | ||
|
|
77baa30496 | ||
|
|
d3ffefbe2d | ||
|
|
dae3af89d1 | ||
|
|
66e0fb187a | ||
|
|
6305301b5d | ||
|
|
6c206a517d | ||
|
|
39e6d6ef99 | ||
|
|
b973016f9e | ||
|
|
b6372f1196 | ||
|
|
dd5bb41f5c | ||
|
|
463c812a33 | ||
|
|
4d8f3725b0 |
58 changed files with 3414 additions and 429 deletions
|
|
@ -11,7 +11,6 @@ OUTPUT_DIRECTORY = @DOXYGEN_OUTPUT_DIRECTORY@
|
|||
CREATE_SUBDIRS = @DOXYGEN_CREATE_SUBDIRS@
|
||||
ALLOW_UNICODE_NAMES = @DOXYGEN_ALLOW_UNICODE_NAMES@
|
||||
OUTPUT_LANGUAGE = @DOXYGEN_OUTPUT_LANGUAGE@
|
||||
OUTPUT_TEXT_DIRECTION = @DOXYGEN_OUTPUT_TEXT_DIRECTION@
|
||||
BRIEF_MEMBER_DESC = @DOXYGEN_BRIEF_MEMBER_DESC@
|
||||
REPEAT_BRIEF = @DOXYGEN_REPEAT_BRIEF@
|
||||
ABBREVIATE_BRIEF = @DOXYGEN_ABBREVIATE_BRIEF@
|
||||
|
|
@ -22,7 +21,6 @@ STRIP_FROM_PATH = @DOXYGEN_STRIP_FROM_PATH@
|
|||
STRIP_FROM_INC_PATH = @DOXYGEN_STRIP_FROM_INC_PATH@
|
||||
SHORT_NAMES = @DOXYGEN_SHORT_NAMES@
|
||||
JAVADOC_AUTOBRIEF = @DOXYGEN_JAVADOC_AUTOBRIEF@
|
||||
JAVADOC_BANNER = @DOXYGEN_JAVADOC_BANNER@
|
||||
QT_AUTOBRIEF = @DOXYGEN_QT_AUTOBRIEF@
|
||||
MULTILINE_CPP_IS_BRIEF = @DOXYGEN_MULTILINE_CPP_IS_BRIEF@
|
||||
INHERIT_DOCS = @DOXYGEN_INHERIT_DOCS@
|
||||
|
|
@ -34,7 +32,6 @@ OPTIMIZE_OUTPUT_FOR_C = @DOXYGEN_OPTIMIZE_OUTPUT_FOR_C@
|
|||
OPTIMIZE_OUTPUT_JAVA = @DOXYGEN_OPTIMIZE_OUTPUT_JAVA@
|
||||
OPTIMIZE_FOR_FORTRAN = @DOXYGEN_OPTIMIZE_FOR_FORTRAN@
|
||||
OPTIMIZE_OUTPUT_VHDL = @DOXYGEN_OPTIMIZE_OUTPUT_VHDL@
|
||||
OPTIMIZE_OUTPUT_SLICE = @DOXYGEN_OPTIMIZE_OUTPUT_SLICE@
|
||||
EXTENSION_MAPPING = @DOXYGEN_EXTENSION_MAPPING@
|
||||
MARKDOWN_SUPPORT = @DOXYGEN_MARKDOWN_SUPPORT@
|
||||
TOC_INCLUDE_HEADINGS = @DOXYGEN_TOC_INCLUDE_HEADINGS@
|
||||
|
|
@ -52,7 +49,6 @@ TYPEDEF_HIDES_STRUCT = @DOXYGEN_TYPEDEF_HIDES_STRUCT@
|
|||
LOOKUP_CACHE_SIZE = @DOXYGEN_LOOKUP_CACHE_SIZE@
|
||||
EXTRACT_ALL = @DOXYGEN_EXTRACT_ALL@
|
||||
EXTRACT_PRIVATE = @DOXYGEN_EXTRACT_PRIVATE@
|
||||
EXTRACT_PRIV_VIRTUAL = @DOXYGEN_EXTRACT_PRIV_VIRTUAL@
|
||||
EXTRACT_PACKAGE = @DOXYGEN_EXTRACT_PACKAGE@
|
||||
EXTRACT_STATIC = @DOXYGEN_EXTRACT_STATIC@
|
||||
EXTRACT_LOCAL_CLASSES = @DOXYGEN_EXTRACT_LOCAL_CLASSES@
|
||||
|
|
@ -124,7 +120,6 @@ USE_HTAGS = @DOXYGEN_USE_HTAGS@
|
|||
VERBATIM_HEADERS = @DOXYGEN_VERBATIM_HEADERS@
|
||||
CLANG_ASSISTED_PARSING = @DOXYGEN_CLANG_ASSISTED_PARSING@
|
||||
CLANG_OPTIONS = @DOXYGEN_CLANG_OPTIONS@
|
||||
CLANG_DATABASE_PATH = @DOXYGEN_CLANG_DATABASE_PATH@
|
||||
ALPHABETICAL_INDEX = @DOXYGEN_ALPHABETICAL_INDEX@
|
||||
COLS_IN_ALPHA_INDEX = @DOXYGEN_COLS_IN_ALPHA_INDEX@
|
||||
IGNORE_PREFIX = @DOXYGEN_IGNORE_PREFIX@
|
||||
|
|
@ -140,7 +135,6 @@ HTML_COLORSTYLE_HUE = @DOXYGEN_HTML_COLORSTYLE_HUE@
|
|||
HTML_COLORSTYLE_SAT = @DOXYGEN_HTML_COLORSTYLE_SAT@
|
||||
HTML_COLORSTYLE_GAMMA = @DOXYGEN_HTML_COLORSTYLE_GAMMA@
|
||||
HTML_TIMESTAMP = @DOXYGEN_HTML_TIMESTAMP@
|
||||
HTML_DYNAMIC_MENUS = @DOXYGEN_HTML_DYNAMIC_MENUS@
|
||||
HTML_DYNAMIC_SECTIONS = @DOXYGEN_HTML_DYNAMIC_SECTIONS@
|
||||
HTML_INDEX_NUM_ENTRIES = @DOXYGEN_HTML_INDEX_NUM_ENTRIES@
|
||||
GENERATE_DOCSET = @DOXYGEN_GENERATE_DOCSET@
|
||||
|
|
@ -188,7 +182,6 @@ GENERATE_LATEX = @DOXYGEN_GENERATE_LATEX@
|
|||
LATEX_OUTPUT = @DOXYGEN_LATEX_OUTPUT@
|
||||
LATEX_CMD_NAME = @DOXYGEN_LATEX_CMD_NAME@
|
||||
MAKEINDEX_CMD_NAME = @DOXYGEN_MAKEINDEX_CMD_NAME@
|
||||
LATEX_MAKEINDEX_CMD = @DOXYGEN_LATEX_MAKEINDEX_CMD@
|
||||
COMPACT_LATEX = @DOXYGEN_COMPACT_LATEX@
|
||||
PAPER_TYPE = @DOXYGEN_PAPER_TYPE@
|
||||
EXTRA_PACKAGES = @DOXYGEN_EXTRA_PACKAGES@
|
||||
|
|
@ -203,7 +196,6 @@ LATEX_HIDE_INDICES = @DOXYGEN_LATEX_HIDE_INDICES@
|
|||
LATEX_SOURCE_CODE = @DOXYGEN_LATEX_SOURCE_CODE@
|
||||
LATEX_BIB_STYLE = @DOXYGEN_LATEX_BIB_STYLE@
|
||||
LATEX_TIMESTAMP = @DOXYGEN_LATEX_TIMESTAMP@
|
||||
LATEX_EMOJI_DIRECTORY = @DOXYGEN_LATEX_EMOJI_DIRECTORY@
|
||||
GENERATE_RTF = @DOXYGEN_GENERATE_RTF@
|
||||
RTF_OUTPUT = @DOXYGEN_RTF_OUTPUT@
|
||||
COMPACT_RTF = @DOXYGEN_COMPACT_RTF@
|
||||
|
|
@ -219,7 +211,6 @@ MAN_LINKS = @DOXYGEN_MAN_LINKS@
|
|||
GENERATE_XML = @DOXYGEN_GENERATE_XML@
|
||||
XML_OUTPUT = @DOXYGEN_XML_OUTPUT@
|
||||
XML_PROGRAMLISTING = @DOXYGEN_XML_PROGRAMLISTING@
|
||||
XML_NS_MEMB_FILE_SCOPE = @DOXYGEN_XML_NS_MEMB_FILE_SCOPE@
|
||||
GENERATE_DOCBOOK = @DOXYGEN_GENERATE_DOCBOOK@
|
||||
DOCBOOK_OUTPUT = @DOXYGEN_DOCBOOK_OUTPUT@
|
||||
DOCBOOK_PROGRAMLISTING = @DOXYGEN_DOCBOOK_PROGRAMLISTING@
|
||||
|
|
@ -242,7 +233,9 @@ GENERATE_TAGFILE = @DOXYGEN_GENERATE_TAGFILE@
|
|||
ALLEXTERNALS = @DOXYGEN_ALLEXTERNALS@
|
||||
EXTERNAL_GROUPS = @DOXYGEN_EXTERNAL_GROUPS@
|
||||
EXTERNAL_PAGES = @DOXYGEN_EXTERNAL_PAGES@
|
||||
PERL_PATH = @DOXYGEN_PERL_PATH@
|
||||
CLASS_DIAGRAMS = @DOXYGEN_CLASS_DIAGRAMS@
|
||||
MSCGEN_PATH = @DOXYGEN_MSCGEN_PATH@
|
||||
DIA_PATH = @DOXYGEN_DIA_PATH@
|
||||
HIDE_UNDOC_RELATIONS = @DOXYGEN_HIDE_UNDOC_RELATIONS@
|
||||
HAVE_DOT = @DOXYGEN_HAVE_DOT@
|
||||
|
|
|
|||
|
|
@ -539,6 +539,12 @@ namespace graphene { namespace app {
|
|||
break;
|
||||
case impl_global_betting_statistics_object_type:
|
||||
break;
|
||||
case impl_collateral_bid_object_type:{
|
||||
const auto& aobj = dynamic_cast<const collateral_bid_object*>(obj);
|
||||
assert( aobj != nullptr );
|
||||
accounts.insert( aobj->bidder );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -481,7 +481,7 @@ void_result asset_update_evaluator::do_apply(const asset_update_operation& o)
|
|||
database& d = db();
|
||||
|
||||
// If we are now disabling force settlements, cancel all open force settlement orders
|
||||
if( o.new_options.flags & disable_force_settle && asset_to_update->can_force_settle() )
|
||||
if( (o.new_options.flags & disable_force_settle) && asset_to_update->can_force_settle() )
|
||||
{
|
||||
const auto& idx = d.get_index_type<force_settlement_index>().indices().get<by_expiration>();
|
||||
// Funky iteration code because we're removing objects as we go. We have to re-initialize itr every loop instead
|
||||
|
|
@ -489,7 +489,7 @@ void_result asset_update_evaluator::do_apply(const asset_update_operation& o)
|
|||
for( auto itr = idx.lower_bound(o.asset_to_update);
|
||||
itr != idx.end() && itr->settlement_asset_id() == o.asset_to_update;
|
||||
itr = idx.lower_bound(o.asset_to_update) )
|
||||
d.cancel_order(*itr);
|
||||
d.cancel_settle_order(*itr);
|
||||
}
|
||||
|
||||
// For market-issued assets, if core change rate changed, update flag in bitasset data
|
||||
|
|
@ -515,57 +515,366 @@ void_result asset_update_evaluator::do_apply(const asset_update_operation& o)
|
|||
return void_result();
|
||||
} FC_CAPTURE_AND_RETHROW( (o) ) }
|
||||
|
||||
void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bitasset_operation& o)
|
||||
/****************
|
||||
* Loop through assets, looking for ones that are backed by the asset being changed. When found,
|
||||
* perform checks to verify validity
|
||||
*
|
||||
* @param d the database
|
||||
* @param op the bitasset update operation being performed
|
||||
* @param new_backing_asset
|
||||
* @param true if after hf 922/931 (if nothing triggers, this and the logic that depends on it
|
||||
* should be removed).
|
||||
*/
|
||||
void check_children_of_bitasset(database& d, const asset_update_bitasset_operation& op,
|
||||
const asset_object& new_backing_asset, bool after_hf_922_931)
|
||||
{
|
||||
// no need to do these checks if the new backing asset is CORE
|
||||
if ( new_backing_asset.get_id() == asset_id_type() )
|
||||
return;
|
||||
|
||||
// loop through all assets that have this asset as a backing asset
|
||||
const auto& idx = d.get_index_type<asset_index>().indices().get<by_type>();
|
||||
|
||||
for( auto itr = idx.lower_bound(true); itr != idx.end(); ++itr )
|
||||
{
|
||||
const auto& child = *itr;
|
||||
if ( child.bitasset_data(d).options.short_backing_asset == op.asset_to_update )
|
||||
{
|
||||
if ( after_hf_922_931 )
|
||||
{
|
||||
FC_ASSERT( child.get_id() != op.new_options.short_backing_asset,
|
||||
"A BitAsset would be invalidated by changing this backing asset ('A' backed by 'B' backed by 'A')." );
|
||||
|
||||
FC_ASSERT( child.issuer != GRAPHENE_COMMITTEE_ACCOUNT,
|
||||
"A blockchain-controlled market asset would be invalidated by changing this backing asset." );
|
||||
|
||||
FC_ASSERT( !new_backing_asset.is_market_issued(),
|
||||
"A non-blockchain controlled BitAsset would be invalidated by changing this backing asset.");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if( child.get_id() == op.new_options.short_backing_asset )
|
||||
{
|
||||
wlog( "Before hf-922-931, modified an asset to be backed by another, but would cause a continuous "
|
||||
"loop. A cannot be backed by B which is backed by A." );
|
||||
return;
|
||||
}
|
||||
|
||||
if( child.issuer == GRAPHENE_COMMITTEE_ACCOUNT )
|
||||
{
|
||||
wlog( "before hf-922-931, modified an asset to be backed by a non-CORE, but this asset "
|
||||
"is a backing asset for a committee-issued asset. This occurred at block ${b}",
|
||||
("b", d.head_block_num()));
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( new_backing_asset.is_market_issued() ) { // a.k.a. !UIA
|
||||
wlog( "before hf-922-931, modified an asset to be backed by an MPA, but this asset "
|
||||
"is a backing asset for another MPA, which would cause MPA backed by MPA backed by MPA. "
|
||||
"This occurred at block ${b}",
|
||||
("b", d.head_block_num()));
|
||||
return;
|
||||
}
|
||||
} // if child.issuer
|
||||
} // if hf 922/931
|
||||
} // if this child is backed by the asset being adjusted
|
||||
} // for each asset
|
||||
} // check_children_of_bitasset
|
||||
|
||||
void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bitasset_operation& op)
|
||||
{ try {
|
||||
database& d = db();
|
||||
|
||||
const asset_object& a = o.asset_to_update(d);
|
||||
const asset_object& asset_obj = op.asset_to_update(d);
|
||||
|
||||
FC_ASSERT(a.is_market_issued(), "Cannot update BitAsset-specific settings on a non-BitAsset.");
|
||||
FC_ASSERT( asset_obj.is_market_issued(), "Cannot update BitAsset-specific settings on a non-BitAsset." );
|
||||
|
||||
const asset_bitasset_data_object& b = a.bitasset_data(d);
|
||||
FC_ASSERT( !b.has_settlement(), "Cannot update a bitasset after a settlement has executed" );
|
||||
if( o.new_options.short_backing_asset != b.options.short_backing_asset )
|
||||
FC_ASSERT( op.issuer == asset_obj.issuer, "Only asset issuer can update bitasset_data of the asset." );
|
||||
|
||||
const asset_bitasset_data_object& current_bitasset_data = asset_obj.bitasset_data(d);
|
||||
|
||||
FC_ASSERT( !current_bitasset_data.has_settlement(), "Cannot update a bitasset after a global settlement has executed" );
|
||||
|
||||
bool after_hf_core_922_931 = ( d.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_922_931_TIME );
|
||||
|
||||
// Are we changing the backing asset?
|
||||
if( op.new_options.short_backing_asset != current_bitasset_data.options.short_backing_asset )
|
||||
{
|
||||
FC_ASSERT(a.dynamic_asset_data_id(d).current_supply == 0);
|
||||
FC_ASSERT(d.find_object(o.new_options.short_backing_asset));
|
||||
FC_ASSERT( asset_obj.dynamic_asset_data_id(d).current_supply == 0,
|
||||
"Cannot update a bitasset if there is already a current supply." );
|
||||
|
||||
if( a.issuer == GRAPHENE_COMMITTEE_ACCOUNT )
|
||||
const asset_object& new_backing_asset = op.new_options.short_backing_asset(d); // check if the asset exists
|
||||
|
||||
if( after_hf_core_922_931 ) // TODO remove this check after hard fork if things in `else` did not occur
|
||||
{
|
||||
const asset_object& backing = a.bitasset_data(d).options.short_backing_asset(d);
|
||||
if( backing.is_market_issued() )
|
||||
FC_ASSERT( op.new_options.short_backing_asset != asset_obj.get_id(),
|
||||
"Cannot update an asset to be backed by itself." );
|
||||
|
||||
if( current_bitasset_data.is_prediction_market )
|
||||
{
|
||||
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.");
|
||||
FC_ASSERT( asset_obj.precision == new_backing_asset.precision,
|
||||
"The precision of the asset and backing asset must be equal." );
|
||||
}
|
||||
|
||||
if( asset_obj.issuer == GRAPHENE_COMMITTEE_ACCOUNT )
|
||||
{
|
||||
if( new_backing_asset.is_market_issued() )
|
||||
{
|
||||
FC_ASSERT( new_backing_asset.bitasset_data(d).options.short_backing_asset == asset_id_type(),
|
||||
"May not modify a blockchain-controlled market asset to be backed by an asset which is not "
|
||||
"backed by CORE." );
|
||||
|
||||
check_children_of_bitasset( d, op, new_backing_asset, after_hf_core_922_931 );
|
||||
}
|
||||
else
|
||||
{
|
||||
FC_ASSERT( new_backing_asset.get_id() == asset_id_type(),
|
||||
"May not modify a blockchain-controlled market asset to be backed by an asset which is not "
|
||||
"market issued asset nor CORE." );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// not a committee issued asset
|
||||
|
||||
// If we're changing to a backing_asset that is not CORE, we need to look at any
|
||||
// asset ( "CHILD" ) that has this one as a backing asset. If CHILD is committee-owned,
|
||||
// the change is not allowed. If CHILD is user-owned, then this asset's backing
|
||||
// asset must be either CORE or a UIA.
|
||||
if ( new_backing_asset.get_id() != asset_id_type() ) // not backed by CORE
|
||||
{
|
||||
check_children_of_bitasset( d, op, new_backing_asset, after_hf_core_922_931 );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Check if the new backing asset is itself backed by something. It must be CORE or a UIA
|
||||
if ( new_backing_asset.is_market_issued() )
|
||||
{
|
||||
const asset_object& backing_backing_asset = new_backing_asset.bitasset_data(d).asset_id(d);
|
||||
FC_ASSERT( !backing_backing_asset.is_market_issued(), "A BitAsset cannot be backed by a BitAsset that itself "
|
||||
"is backed by a BitAsset.");
|
||||
}
|
||||
}
|
||||
else // prior to HF 922 / 931
|
||||
{
|
||||
// code to check if issues occurred before hard fork. TODO cleanup after hard fork
|
||||
if( op.new_options.short_backing_asset == asset_obj.get_id() )
|
||||
{
|
||||
wlog( "before hf-922-931, op.new_options.short_backing_asset == asset_obj.get_id() at block ${b}",
|
||||
("b",d.head_block_num()) );
|
||||
}
|
||||
if( current_bitasset_data.is_prediction_market && asset_obj.precision != new_backing_asset.precision )
|
||||
{
|
||||
wlog( "before hf-922-931, for a PM, asset_obj.precision != new_backing_asset.precision at block ${b}",
|
||||
("b",d.head_block_num()) );
|
||||
}
|
||||
|
||||
if( asset_obj.issuer == GRAPHENE_COMMITTEE_ACCOUNT )
|
||||
{
|
||||
// code to check if issues occurred before hard fork. TODO cleanup after hard fork
|
||||
if( new_backing_asset.is_market_issued() )
|
||||
{
|
||||
if( new_backing_asset.bitasset_data(d).options.short_backing_asset != asset_id_type() )
|
||||
wlog( "before hf-922-931, modified a blockchain-controlled market asset to be backed by an asset "
|
||||
"which is not backed by CORE at block ${b}",
|
||||
("b",d.head_block_num()) );
|
||||
|
||||
check_children_of_bitasset( d, op, new_backing_asset, after_hf_core_922_931 );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( new_backing_asset.get_id() != asset_id_type() )
|
||||
wlog( "before hf-922-931, modified a blockchain-controlled market asset to be backed by an asset "
|
||||
"which is not market issued asset nor CORE at block ${b}",
|
||||
("b",d.head_block_num()) );
|
||||
}
|
||||
|
||||
//prior to HF 922_931, these checks were mistakenly using the old backing_asset
|
||||
const asset_object& old_backing_asset = current_bitasset_data.options.short_backing_asset(d);
|
||||
|
||||
if( old_backing_asset.is_market_issued() )
|
||||
{
|
||||
FC_ASSERT( old_backing_asset.bitasset_data(d).options.short_backing_asset == asset_id_type(),
|
||||
"May not modify a blockchain-controlled market asset to be backed by an asset which is not "
|
||||
"backed by CORE." );
|
||||
}
|
||||
else
|
||||
{
|
||||
FC_ASSERT( old_backing_asset.get_id() == asset_id_type(),
|
||||
"May not modify a blockchain-controlled market asset to be backed by an asset which is not "
|
||||
"market issued asset nor CORE." );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// not a committee issued asset
|
||||
|
||||
// If we're changing to a backing_asset that is not CORE, we need to look at any
|
||||
// asset ( "CHILD" ) that has this one as a backing asset. If CHILD is committee-owned,
|
||||
// the change is not allowed. If CHILD is user-owned, then this asset's backing
|
||||
// asset must be either CORE or a UIA.
|
||||
if ( new_backing_asset.get_id() != asset_id_type() ) // not backed by CORE
|
||||
{
|
||||
check_children_of_bitasset( d, op, new_backing_asset, after_hf_core_922_931 );
|
||||
}
|
||||
}
|
||||
// if the new backing asset is backed by something which is not CORE and not a UIA, this is not allowed
|
||||
// Check if the new backing asset is itself backed by something. It must be CORE or a UIA
|
||||
if ( new_backing_asset.is_market_issued() )
|
||||
{
|
||||
const asset_object& backing_backing_asset = new_backing_asset.bitasset_data(d).asset_id(d);
|
||||
if ( backing_backing_asset.get_id() != asset_id_type() )
|
||||
{
|
||||
if ( backing_backing_asset.is_market_issued() )
|
||||
{
|
||||
wlog( "before hf-922-931, a BitAsset cannot be backed by a BitAsset that itself "
|
||||
"is backed by a BitAsset. This occurred at block ${b}",
|
||||
("b", d.head_block_num() ) );
|
||||
}
|
||||
} // not core
|
||||
} // if market issued
|
||||
}
|
||||
}
|
||||
|
||||
bitasset_to_update = &b;
|
||||
FC_ASSERT( o.issuer == a.issuer, "", ("o.issuer", o.issuer)("a.issuer", a.issuer) );
|
||||
const auto& chain_parameters = d.get_global_properties().parameters;
|
||||
if( after_hf_core_922_931 ) // TODO remove this check after hard fork if things in `else` did not occur
|
||||
{
|
||||
FC_ASSERT( op.new_options.feed_lifetime_sec > chain_parameters.block_interval,
|
||||
"Feed lifetime must exceed block interval." );
|
||||
FC_ASSERT( op.new_options.force_settlement_delay_sec > chain_parameters.block_interval,
|
||||
"Force settlement delay must exceed block interval." );
|
||||
}
|
||||
else // code to check if issues occurred before hard fork. TODO cleanup after hard fork
|
||||
{
|
||||
if( op.new_options.feed_lifetime_sec <= chain_parameters.block_interval )
|
||||
wlog( "before hf-922-931, op.new_options.feed_lifetime_sec <= chain_parameters.block_interval at block ${b}",
|
||||
("b",d.head_block_num()) );
|
||||
if( op.new_options.force_settlement_delay_sec <= chain_parameters.block_interval )
|
||||
wlog( "before hf-922-931, op.new_options.force_settlement_delay_sec <= chain_parameters.block_interval at block ${b}",
|
||||
("b",d.head_block_num()) );
|
||||
}
|
||||
|
||||
bitasset_to_update = ¤t_bitasset_data;
|
||||
asset_to_update = &asset_obj;
|
||||
|
||||
return void_result();
|
||||
} FC_CAPTURE_AND_RETHROW( (o) ) }
|
||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||
|
||||
void_result asset_update_bitasset_evaluator::do_apply(const asset_update_bitasset_operation& o)
|
||||
{ try {
|
||||
bool should_update_feeds = false;
|
||||
/*******
|
||||
* @brief Apply requested changes to bitasset options
|
||||
*
|
||||
* This applies the requested changes to the bitasset object. It also cleans up the
|
||||
* releated feeds
|
||||
*
|
||||
* @param op the requested operation
|
||||
* @param db the database
|
||||
* @param bdo the actual database object
|
||||
* @param asset_to_update the asset_object related to this bitasset_data_object
|
||||
* @returns true if the feed price is changed, and after hf core-868-890
|
||||
*/
|
||||
static bool update_bitasset_object_options(
|
||||
const asset_update_bitasset_operation& op, database& db,
|
||||
asset_bitasset_data_object& bdo, const asset_object& asset_to_update )
|
||||
{
|
||||
const fc::time_point_sec& next_maint_time = db.get_dynamic_global_properties().next_maintenance_time;
|
||||
bool after_hf_core_868_890 = ( next_maint_time > HARDFORK_CORE_868_890_TIME );
|
||||
// If the minimum number of feeds to calculate a median has changed, we need to recalculate the median
|
||||
if( o.new_options.minimum_feeds != bitasset_to_update->options.minimum_feeds )
|
||||
bool should_update_feeds = false;
|
||||
if( op.new_options.minimum_feeds != bdo.options.minimum_feeds )
|
||||
should_update_feeds = true;
|
||||
|
||||
db().modify(*bitasset_to_update, [&](asset_bitasset_data_object& b) {
|
||||
b.options = o.new_options;
|
||||
// after hardfork core-868-890, we also should call update_median_feeds if the feed_lifetime_sec changed
|
||||
if( after_hf_core_868_890
|
||||
&& op.new_options.feed_lifetime_sec != bdo.options.feed_lifetime_sec )
|
||||
{
|
||||
should_update_feeds = true;
|
||||
}
|
||||
|
||||
if( should_update_feeds )
|
||||
b.update_median_feeds(db().head_block_time());
|
||||
});
|
||||
// feeds must be reset if the backing asset is changed after hardfork core-868-890
|
||||
bool backing_asset_changed = false;
|
||||
bool is_witness_or_committee_fed = false;
|
||||
if( after_hf_core_868_890
|
||||
&& op.new_options.short_backing_asset != bdo.options.short_backing_asset )
|
||||
{
|
||||
backing_asset_changed = true;
|
||||
should_update_feeds = true;
|
||||
if ( asset_to_update.options.flags & (witness_fed_asset | committee_fed_asset) )
|
||||
is_witness_or_committee_fed = true;
|
||||
}
|
||||
|
||||
return void_result();
|
||||
} FC_CAPTURE_AND_RETHROW( (o) ) }
|
||||
bdo.options = op.new_options;
|
||||
|
||||
// are we modifying the underlying? If so, reset the feeds
|
||||
if (backing_asset_changed)
|
||||
{
|
||||
if ( is_witness_or_committee_fed )
|
||||
{
|
||||
bdo.feeds.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// for non-witness-feeding and non-committee-feeding assets, modify all feeds
|
||||
// published by producers to nothing, since we can't simply remove them. For more information:
|
||||
// https://github.com/bitshares/bitshares-core/pull/832#issuecomment-384112633
|
||||
for(auto& current_feed : bdo.feeds)
|
||||
{
|
||||
current_feed.second.second.settlement_price = price();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( should_update_feeds )
|
||||
{
|
||||
const auto old_feed = bdo.current_feed;
|
||||
bdo.update_median_feeds( db.head_block_time(), next_maint_time );
|
||||
|
||||
// TODO review and refactor / cleanup after hard fork:
|
||||
// 1. if hf_core_868_890 and core-935 occurred at same time
|
||||
// 2. if wlog did not actually get called
|
||||
|
||||
// We need to call check_call_orders if the price feed changes after hardfork core-935
|
||||
if( next_maint_time > HARDFORK_CORE_935_TIME )
|
||||
return ( !( old_feed == bdo.current_feed ) );
|
||||
|
||||
// We need to call check_call_orders if the settlement price changes after hardfork core-868-890
|
||||
if( after_hf_core_868_890 )
|
||||
{
|
||||
if( old_feed.settlement_price != bdo.current_feed.settlement_price )
|
||||
return true;
|
||||
else
|
||||
{
|
||||
if( !( old_feed == bdo.current_feed ) )
|
||||
wlog( "Settlement price did not change but current_feed changed at block ${b}", ("b",db.head_block_num()) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void_result asset_update_bitasset_evaluator::do_apply(const asset_update_bitasset_operation& op)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto& db_conn = db();
|
||||
const auto& asset_being_updated = (*asset_to_update);
|
||||
bool to_check_call_orders = false;
|
||||
|
||||
db_conn.modify( *bitasset_to_update,
|
||||
[&op, &asset_being_updated, &to_check_call_orders, &db_conn]( asset_bitasset_data_object& bdo )
|
||||
{
|
||||
to_check_call_orders = update_bitasset_object_options( op, db_conn, bdo, asset_being_updated );
|
||||
});
|
||||
|
||||
if( to_check_call_orders )
|
||||
db_conn.check_call_orders( asset_being_updated );
|
||||
|
||||
return void_result();
|
||||
} FC_CAPTURE_AND_RETHROW( (op) )
|
||||
}
|
||||
|
||||
void_result asset_update_dividend_evaluator::do_evaluate(const asset_update_dividend_operation& o)
|
||||
{ try {
|
||||
|
|
@ -652,6 +961,7 @@ void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_updat
|
|||
|
||||
void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_feed_producers_evaluator::operation_type& o)
|
||||
{ try {
|
||||
const auto next_maint_time = db().get_dynamic_global_properties().next_maintenance_time;
|
||||
db().modify(*bitasset_to_update, [&](asset_bitasset_data_object& a) {
|
||||
//This is tricky because I have a set of publishers coming in, but a map of publisher to feed is stored.
|
||||
//I need to update the map such that the keys match the new publishers, but not munge the old price feeds from
|
||||
|
|
@ -668,7 +978,7 @@ void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_f
|
|||
for( auto itr = o.new_feed_producers.begin(); itr != o.new_feed_producers.end(); ++itr )
|
||||
if( !a.feeds.count(*itr) )
|
||||
a.feeds[*itr];
|
||||
a.update_median_feeds(db().head_block_time());
|
||||
a.update_median_feeds(db().head_block_time(), next_maint_time);
|
||||
});
|
||||
db().check_call_orders( o.asset_to_update(db()) );
|
||||
|
||||
|
|
@ -683,6 +993,13 @@ void_result asset_global_settle_evaluator::do_evaluate(const asset_global_settle
|
|||
FC_ASSERT(asset_to_settle->can_global_settle());
|
||||
FC_ASSERT(asset_to_settle->issuer == op.issuer );
|
||||
FC_ASSERT(asset_to_settle->dynamic_data(d).current_supply > 0);
|
||||
|
||||
const asset_bitasset_data_object* bitasset_data = &asset_to_settle->bitasset_data(d);
|
||||
if( bitasset_data->is_prediction_market ) {
|
||||
/// if there is a settlement for this asset, then no further global settle may be taken and
|
||||
FC_ASSERT( !bitasset_data->has_settlement(),"This asset has settlement, cannot global settle twice" );
|
||||
}
|
||||
|
||||
const auto& idx = d.get_index_type<call_order_index>().indices().get<by_collateral>();
|
||||
assert( !idx.empty() );
|
||||
auto itr = idx.lower_bound(boost::make_tuple(price::min(asset_to_settle->bitasset_data(d).options.short_backing_asset,
|
||||
|
|
@ -726,25 +1043,49 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator::
|
|||
const auto& bitasset = asset_to_settle->bitasset_data(d);
|
||||
if( bitasset.has_settlement() )
|
||||
{
|
||||
auto settled_amount = op.amount * bitasset.settlement_price;
|
||||
FC_ASSERT( settled_amount.amount <= bitasset.settlement_fund );
|
||||
|
||||
d.modify( bitasset, [&]( asset_bitasset_data_object& obj ){
|
||||
obj.settlement_fund -= settled_amount.amount;
|
||||
});
|
||||
|
||||
d.adjust_balance(op.account, settled_amount);
|
||||
|
||||
const auto& mia_dyn = asset_to_settle->dynamic_asset_data_id(d);
|
||||
auto settled_amount = op.amount * bitasset.settlement_price;
|
||||
if( op.amount.amount == mia_dyn.current_supply )
|
||||
settled_amount.amount = bitasset.settlement_fund; // avoid rounding problems
|
||||
else
|
||||
FC_ASSERT( settled_amount.amount < bitasset.settlement_fund );
|
||||
|
||||
if( settled_amount.amount == 0 && !bitasset.is_prediction_market )
|
||||
{
|
||||
if( d.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME )
|
||||
FC_THROW( "Settle amount is too small to receive anything due to rounding" );
|
||||
else // TODO remove this warning after hard fork core-184
|
||||
wlog( "Something for nothing issue (#184, variant F) occurred at block #${block}", ("block",d.head_block_num()) );
|
||||
}
|
||||
|
||||
asset pays = op.amount;
|
||||
if( op.amount.amount != mia_dyn.current_supply
|
||||
&& settled_amount.amount != 0
|
||||
&& d.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_342_TIME )
|
||||
{
|
||||
pays = settled_amount.multiply_and_round_up( bitasset.settlement_price );
|
||||
}
|
||||
|
||||
d.adjust_balance( op.account, -pays );
|
||||
|
||||
if( settled_amount.amount > 0 )
|
||||
{
|
||||
d.modify( bitasset, [&]( asset_bitasset_data_object& obj ){
|
||||
obj.settlement_fund -= settled_amount.amount;
|
||||
});
|
||||
|
||||
d.adjust_balance( op.account, settled_amount );
|
||||
}
|
||||
|
||||
d.modify( mia_dyn, [&]( asset_dynamic_data_object& obj ){
|
||||
obj.current_supply -= op.amount.amount;
|
||||
});
|
||||
obj.current_supply -= pays.amount;
|
||||
});
|
||||
|
||||
return settled_amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
d.adjust_balance( op.account, -op.amount );
|
||||
return d.create<force_settlement_object>([&](force_settlement_object& s) {
|
||||
s.owner = op.account;
|
||||
s.balance = op.amount;
|
||||
|
|
@ -762,7 +1103,10 @@ void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_
|
|||
FC_ASSERT(base.is_market_issued());
|
||||
|
||||
const asset_bitasset_data_object& bitasset = base.bitasset_data(d);
|
||||
FC_ASSERT( !bitasset.has_settlement(), "No further feeds may be published after a settlement event" );
|
||||
if( bitasset.is_prediction_market || d.head_block_time() <= HARDFORK_CORE_216_TIME )
|
||||
{
|
||||
FC_ASSERT( !bitasset.has_settlement(), "No further feeds may be published after a settlement event" );
|
||||
}
|
||||
|
||||
FC_ASSERT( o.feed.settlement_price.quote.asset_id == bitasset.options.short_backing_asset );
|
||||
if( d.head_block_time() > HARDFORK_480_TIME )
|
||||
|
|
@ -801,19 +1145,52 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope
|
|||
{ try {
|
||||
|
||||
database& d = db();
|
||||
const auto head_time = d.head_block_time();
|
||||
const auto next_maint_time = d.get_dynamic_global_properties().next_maintenance_time;
|
||||
|
||||
const asset_object& base = o.asset_id(d);
|
||||
const asset_bitasset_data_object& bad = base.bitasset_data(d);
|
||||
|
||||
auto old_feed = bad.current_feed;
|
||||
// Store medians for this asset
|
||||
d.modify(bad , [&o,&d](asset_bitasset_data_object& a) {
|
||||
a.feeds[o.publisher] = make_pair(d.head_block_time(), o.feed);
|
||||
a.update_median_feeds(d.head_block_time());
|
||||
d.modify( bad , [&o,head_time,next_maint_time](asset_bitasset_data_object& a) {
|
||||
a.feeds[o.publisher] = make_pair( head_time, o.feed );
|
||||
a.update_median_feeds( head_time, next_maint_time );
|
||||
});
|
||||
|
||||
if( !(old_feed == bad.current_feed) )
|
||||
{
|
||||
// Check whether need to revive the asset and proceed if need
|
||||
if( bad.has_settlement() // has globally settled, implies head_block_time > HARDFORK_CORE_216_TIME
|
||||
&& !bad.current_feed.settlement_price.is_null() ) // has a valid feed
|
||||
{
|
||||
bool should_revive = false;
|
||||
const auto& mia_dyn = base.dynamic_asset_data_id(d);
|
||||
if( mia_dyn.current_supply == 0 ) // if current supply is zero, revive the asset
|
||||
should_revive = true;
|
||||
else // if current supply is not zero, when collateral ratio of settlement fund is greater than MCR, revive the asset
|
||||
{
|
||||
if( next_maint_time <= HARDFORK_CORE_1270_TIME )
|
||||
{
|
||||
// before core-1270 hard fork, calculate call_price and compare to median feed
|
||||
if( ~price::call_price( asset(mia_dyn.current_supply, o.asset_id),
|
||||
asset(bad.settlement_fund, bad.options.short_backing_asset),
|
||||
bad.current_feed.maintenance_collateral_ratio ) < bad.current_feed.settlement_price )
|
||||
should_revive = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// after core-1270 hard fork, calculate collateralization and compare to maintenance_collateralization
|
||||
if( price( asset( bad.settlement_fund, bad.options.short_backing_asset ),
|
||||
asset( mia_dyn.current_supply, o.asset_id ) ) > bad.current_maintenance_collateralization )
|
||||
should_revive = true;
|
||||
}
|
||||
}
|
||||
if( should_revive )
|
||||
d.revive_bitasset(base);
|
||||
}
|
||||
db().check_call_orders(base);
|
||||
}
|
||||
|
||||
return void_result();
|
||||
} FC_CAPTURE_AND_RETHROW((o)) }
|
||||
|
|
|
|||
|
|
@ -44,10 +44,13 @@ share_type asset_bitasset_data_object::max_force_settlement_volume(share_type cu
|
|||
return volume.to_uint64();
|
||||
}
|
||||
|
||||
void asset_bitasset_data_object::update_median_feeds(time_point_sec current_time)
|
||||
void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_point_sec current_time,
|
||||
time_point_sec next_maintenance_time )
|
||||
{
|
||||
bool after_core_hardfork_1270 = ( next_maintenance_time > HARDFORK_CORE_1270_TIME ); // call price caching issue
|
||||
current_feed_publication_time = current_time;
|
||||
vector<std::reference_wrapper<const price_feed>> current_feeds;
|
||||
// find feeds that were alive at current_time
|
||||
for( const pair<account_id_type, pair<time_point_sec,price_feed>>& f : feeds )
|
||||
{
|
||||
if( (current_time - f.second.first).to_seconds() < options.feed_lifetime_sec &&
|
||||
|
|
@ -65,13 +68,18 @@ void asset_bitasset_data_object::update_median_feeds(time_point_sec current_time
|
|||
feed_cer_updated = false; // new median cer is null, won't update asset_object anyway, set to false for better performance
|
||||
current_feed_publication_time = current_time;
|
||||
current_feed = price_feed();
|
||||
if( after_core_hardfork_1270 )
|
||||
current_maintenance_collateralization = price();
|
||||
return;
|
||||
}
|
||||
if( current_feeds.size() == 1 )
|
||||
{
|
||||
if( current_feed.core_exchange_rate != current_feeds.front().get().core_exchange_rate )
|
||||
feed_cer_updated = true;
|
||||
current_feed = std::move(current_feeds.front());
|
||||
current_feed = current_feeds.front();
|
||||
// Note: perhaps can defer updating current_maintenance_collateralization for better performance
|
||||
if( after_core_hardfork_1270 )
|
||||
current_maintenance_collateralization = current_feed.maintenance_collateralization();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -92,6 +100,9 @@ void asset_bitasset_data_object::update_median_feeds(time_point_sec current_time
|
|||
if( current_feed.core_exchange_rate != median_feed.core_exchange_rate )
|
||||
feed_cer_updated = true;
|
||||
current_feed = median_feed;
|
||||
// Note: perhaps can defer updating current_maintenance_collateralization for better performance
|
||||
if( after_core_hardfork_1270 )
|
||||
current_maintenance_collateralization = current_feed.maintenance_collateralization();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ void database::debug_dump()
|
|||
|
||||
const auto& balance_index = db.get_index_type<account_balance_index>().indices();
|
||||
const auto& statistics_index = db.get_index_type<account_stats_index>().indices();
|
||||
const auto& bids = db.get_index_type<collateral_bid_index>().indices();
|
||||
map<asset_id_type,share_type> total_balances;
|
||||
map<asset_id_type,share_type> total_debts;
|
||||
share_type core_in_orders;
|
||||
|
|
@ -58,6 +59,8 @@ void database::debug_dump()
|
|||
// idump(("statistics")(s));
|
||||
reported_core_in_orders += s.total_core_in_orders;
|
||||
}
|
||||
for( const collateral_bid_object& b : bids )
|
||||
total_balances[b.inv_swan_price.base.asset_id] += b.inv_swan_price.base.amount;
|
||||
for( const limit_order_object& o : db.get_index_type<limit_order_index>().indices() )
|
||||
{
|
||||
// idump(("limit_order")(o));
|
||||
|
|
|
|||
|
|
@ -327,6 +327,7 @@ void database::initialize_evaluators()
|
|||
register_evaluator<sidechain_transaction_send_evaluator>();
|
||||
register_evaluator<sidechain_transaction_settle_evaluator>();
|
||||
register_evaluator<random_number_store_evaluator>();
|
||||
register_evaluator<bid_collateral_evaluator>();
|
||||
}
|
||||
|
||||
void database::initialize_indexes()
|
||||
|
|
@ -419,6 +420,7 @@ void database::initialize_indexes()
|
|||
add_index< primary_index<nft_lottery_balance_index > >();
|
||||
add_index< primary_index<son_stats_index > >();
|
||||
add_index< primary_index<random_number_index > >();
|
||||
add_index< primary_index<collateral_bid_index > >();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1939,6 +1939,353 @@ void database::perform_son_tasks()
|
|||
}
|
||||
}
|
||||
|
||||
/// Reset call_price of all call orders according to their remaining collateral and debt.
|
||||
/// Do not update orders of prediction markets because we're sure they're up to date.
|
||||
void update_call_orders_hf_343( database& db )
|
||||
{
|
||||
// Update call_price
|
||||
wlog( "Updating all call orders for hardfork core-343 at block ${n}", ("n",db.head_block_num()) );
|
||||
asset_id_type current_asset;
|
||||
const asset_bitasset_data_object* abd = nullptr;
|
||||
// by_collateral index won't change after call_price updated, so it's safe to iterate
|
||||
for( const auto& call_obj : db.get_index_type<call_order_index>().indices().get<by_collateral>() )
|
||||
{
|
||||
if( current_asset != call_obj.debt_type() ) // debt type won't be asset_id_type(), abd will always get initialized
|
||||
{
|
||||
current_asset = call_obj.debt_type();
|
||||
abd = ¤t_asset(db).bitasset_data(db);
|
||||
}
|
||||
if( !abd || abd->is_prediction_market ) // nothing to do with PM's; check !abd just to be safe
|
||||
continue;
|
||||
db.modify( call_obj, [&]( call_order_object& call ) {
|
||||
call.call_price = price::call_price( call.get_debt(), call.get_collateral(),
|
||||
abd->current_feed.maintenance_collateral_ratio );
|
||||
});
|
||||
}
|
||||
wlog( "Done updating all call orders for hardfork core-343 at block ${n}", ("n",db.head_block_num()) );
|
||||
}
|
||||
|
||||
/// Reset call_price of all call orders to (1,1) since it won't be used in the future.
|
||||
/// Update PMs as well.
|
||||
void update_call_orders_hf_1270( database& db )
|
||||
{
|
||||
// Update call_price
|
||||
wlog( "Updating all call orders for hardfork core-1270 at block ${n}", ("n",db.head_block_num()) );
|
||||
for( const auto& call_obj : db.get_index_type<call_order_index>().indices().get<by_id>() )
|
||||
{
|
||||
db.modify( call_obj, []( call_order_object& call ) {
|
||||
call.call_price.base.amount = 1;
|
||||
call.call_price.quote.amount = 1;
|
||||
});
|
||||
}
|
||||
wlog( "Done updating all call orders for hardfork core-1270 at block ${n}", ("n",db.head_block_num()) );
|
||||
}
|
||||
|
||||
/// Match call orders for all bitAssets, including PMs.
|
||||
void match_call_orders( database& db )
|
||||
{
|
||||
// Match call orders
|
||||
wlog( "Matching call orders at block ${n}", ("n",db.head_block_num()) );
|
||||
const auto& asset_idx = db.get_index_type<asset_index>().indices().get<by_type>();
|
||||
auto itr = asset_idx.lower_bound( true /** market issued */ );
|
||||
while( itr != asset_idx.end() )
|
||||
{
|
||||
const asset_object& a = *itr;
|
||||
++itr;
|
||||
// be here, next_maintenance_time should have been updated already
|
||||
db.check_call_orders( a, true, false ); // allow black swan, and call orders are taker
|
||||
}
|
||||
wlog( "Done matching call orders at block ${n}", ("n",db.head_block_num()) );
|
||||
}
|
||||
|
||||
void database::process_bids( const asset_bitasset_data_object& bad )
|
||||
{
|
||||
if( bad.current_feed.settlement_price.is_null() ) return;
|
||||
|
||||
asset_id_type to_revive_id = (asset( 0, bad.options.short_backing_asset ) * bad.settlement_price).asset_id;
|
||||
const asset_object& to_revive = to_revive_id( *this );
|
||||
const asset_dynamic_data_object& bdd = to_revive.dynamic_data( *this );
|
||||
|
||||
const auto& bid_idx = get_index_type< collateral_bid_index >().indices().get<by_price>();
|
||||
const auto start = bid_idx.lower_bound( boost::make_tuple( to_revive_id, price::max( bad.options.short_backing_asset, to_revive_id ), collateral_bid_id_type() ) );
|
||||
|
||||
share_type covered = 0;
|
||||
auto itr = start;
|
||||
while( itr != bid_idx.end() && itr->inv_swan_price.quote.asset_id == to_revive_id )
|
||||
{
|
||||
const collateral_bid_object& bid = *itr;
|
||||
asset total_collateral = bid.inv_swan_price.quote * bad.settlement_price;
|
||||
total_collateral += bid.inv_swan_price.base;
|
||||
price call_price = price::call_price( bid.inv_swan_price.quote, total_collateral, bad.current_feed.maintenance_collateral_ratio );
|
||||
if( ~call_price >= bad.current_feed.settlement_price ) break;
|
||||
covered += bid.inv_swan_price.quote.amount;
|
||||
++itr;
|
||||
if( covered >= bdd.current_supply ) break;
|
||||
}
|
||||
if( covered < bdd.current_supply ) return;
|
||||
|
||||
const auto end = itr;
|
||||
share_type to_cover = bdd.current_supply;
|
||||
share_type remaining_fund = bad.settlement_fund;
|
||||
for( itr = start; itr != end; )
|
||||
{
|
||||
const collateral_bid_object& bid = *itr;
|
||||
++itr;
|
||||
share_type debt = bid.inv_swan_price.quote.amount;
|
||||
share_type collateral = (bid.inv_swan_price.quote * bad.settlement_price).amount;
|
||||
if( bid.inv_swan_price.quote.amount >= to_cover )
|
||||
{
|
||||
debt = to_cover;
|
||||
collateral = remaining_fund;
|
||||
}
|
||||
to_cover -= debt;
|
||||
remaining_fund -= collateral;
|
||||
execute_bid( bid, debt, collateral, bad.current_feed );
|
||||
}
|
||||
|
||||
FC_ASSERT( remaining_fund == 0 );
|
||||
FC_ASSERT( to_cover == 0 );
|
||||
|
||||
_cancel_bids_and_revive_mpa( to_revive, bad );
|
||||
}
|
||||
|
||||
void update_median_feeds(database& db)
|
||||
{
|
||||
time_point_sec head_time = db.head_block_time();
|
||||
time_point_sec next_maint_time = db.get_dynamic_global_properties().next_maintenance_time;
|
||||
|
||||
const auto update_bitasset = [head_time, next_maint_time]( asset_bitasset_data_object &o )
|
||||
{
|
||||
o.update_median_feeds( head_time, next_maint_time );
|
||||
};
|
||||
|
||||
for( const auto& d : db.get_index_type<asset_bitasset_data_index>().indices() )
|
||||
{
|
||||
db.modify( d, update_bitasset );
|
||||
}
|
||||
}
|
||||
|
||||
/****
|
||||
* @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
|
||||
*
|
||||
* Prior to hardfork 868, switching a bitasset's shorting asset would not reset its
|
||||
* feeds. This method will run at the hardfork time, and erase (or nullify) feeds
|
||||
* that have incorrect backing assets.
|
||||
* https://github.com/bitshares/bitshares-core/issues/868
|
||||
*
|
||||
* Prior to hardfork 890, changing a bitasset's feed expiration time would not
|
||||
* trigger a median feed update. This method will run at the hardfork time, and
|
||||
* correct all median feed data.
|
||||
* https://github.com/bitshares/bitshares-core/issues/890
|
||||
*
|
||||
* @param db the database
|
||||
* @param skip_check_call_orders true if check_call_orders() should not be called
|
||||
*/
|
||||
// TODO: for better performance, this function can be removed if it actually updated nothing at hf time.
|
||||
// * Also need to update related test cases
|
||||
// * NOTE: perhaps the removal can't be applied to testnet
|
||||
void process_hf_868_890( database& db, bool skip_check_call_orders )
|
||||
{
|
||||
const auto next_maint_time = db.get_dynamic_global_properties().next_maintenance_time;
|
||||
const auto head_time = db.head_block_time();
|
||||
const auto head_num = db.head_block_num();
|
||||
wlog( "Processing hard fork core-868-890 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;
|
||||
// Incorrect witness & committee feeds can simply be removed.
|
||||
// For non-witness-fed and non-committee-fed assets, set incorrect
|
||||
// feeds to price(), since we can't simply remove them. For more information:
|
||||
// https://github.com/bitshares/bitshares-core/pull/832#issuecomment-384112633
|
||||
bool is_witness_or_committee_fed = false;
|
||||
if ( current_asset.options.flags & ( witness_fed_asset | committee_fed_asset ) )
|
||||
is_witness_or_committee_fed = true;
|
||||
|
||||
// for each feed
|
||||
const asset_bitasset_data_object& bitasset_data = current_asset.bitasset_data(db);
|
||||
// NOTE: We'll only need old_feed if HF343 hasn't rolled out yet
|
||||
auto old_feed = bitasset_data.current_feed;
|
||||
bool feeds_changed = false; // did any feed change
|
||||
auto itr = bitasset_data.feeds.begin();
|
||||
while( itr != bitasset_data.feeds.end() )
|
||||
{
|
||||
// If the feed is invalid
|
||||
if ( itr->second.second.settlement_price.quote.asset_id != bitasset_data.options.short_backing_asset
|
||||
&& ( is_witness_or_committee_fed || itr->second.second.settlement_price != price() ) )
|
||||
{
|
||||
feeds_changed = true;
|
||||
db.modify( bitasset_data, [&itr, is_witness_or_committee_fed]( asset_bitasset_data_object& obj )
|
||||
{
|
||||
if( is_witness_or_committee_fed )
|
||||
{
|
||||
// erase the invalid feed
|
||||
itr = obj.feeds.erase(itr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// nullify the invalid feed
|
||||
obj.feeds[itr->first].second.settlement_price = price();
|
||||
++itr;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Feed is valid. Skip it.
|
||||
++itr;
|
||||
}
|
||||
} // end loop of each feed
|
||||
|
||||
// if any feed was modified, print a warning message
|
||||
if( feeds_changed )
|
||||
{
|
||||
wlog( "Found invalid feed for asset ${asset_sym} (${asset_id}) during hardfork core-868-890",
|
||||
("asset_sym", current_asset.symbol)("asset_id", current_asset.id) );
|
||||
}
|
||||
|
||||
// always update the median feed due to https://github.com/bitshares/bitshares-core/issues/890
|
||||
db.modify( bitasset_data, [head_time,next_maint_time]( asset_bitasset_data_object &obj ) {
|
||||
obj.update_median_feeds( head_time, next_maint_time );
|
||||
});
|
||||
|
||||
bool median_changed = ( old_feed.settlement_price != bitasset_data.current_feed.settlement_price );
|
||||
bool median_feed_changed = ( !( old_feed == bitasset_data.current_feed ) );
|
||||
if( median_feed_changed )
|
||||
{
|
||||
wlog( "Median feed for asset ${asset_sym} (${asset_id}) changed during hardfork core-868-890",
|
||||
("asset_sym", current_asset.symbol)("asset_id", current_asset.id) );
|
||||
}
|
||||
// Note: due to bitshares-core issue #935, the check below (using median_changed) is incorrect.
|
||||
// However, `skip_check_call_orders` will likely be true in both testnet and mainnet,
|
||||
// so effectively the incorrect code won't make a difference.
|
||||
// Additionally, we have code to update all call orders again during hardfork core-935
|
||||
// TODO cleanup after hard fork
|
||||
if( !skip_check_call_orders && median_changed ) // check_call_orders should be called
|
||||
{
|
||||
db.check_call_orders( current_asset );
|
||||
}
|
||||
else if( !skip_check_call_orders && median_feed_changed )
|
||||
{
|
||||
wlog( "Incorrectly skipped check_call_orders for asset ${asset_sym} (${asset_id}) during hardfork core-868-890",
|
||||
("asset_sym", current_asset.symbol)("asset_id", current_asset.id) );
|
||||
}
|
||||
} // for each market issued asset
|
||||
wlog( "Done processing hard fork core-868-890 at block ${n}", ("n",head_num) );
|
||||
}
|
||||
|
||||
/******
|
||||
* @brief one-time data process for hard fork core-935
|
||||
*
|
||||
* Prior to hardfork 935, `check_call_orders` may be unintendedly skipped when
|
||||
* median price feed has changed. This method will run at the hardfork time, and
|
||||
* call `check_call_orders` for all markets.
|
||||
* https://github.com/bitshares/bitshares-core/issues/935
|
||||
*
|
||||
* @param db the database
|
||||
*/
|
||||
// TODO: for better performance, this function can be removed if it actually updated nothing at hf time.
|
||||
// * Also need to update related test cases
|
||||
// * NOTE: perhaps the removal can't be applied to testnet
|
||||
void process_hf_935( database& db )
|
||||
{
|
||||
bool changed_something = false;
|
||||
const asset_bitasset_data_object* bitasset = nullptr;
|
||||
bool settled_before_check_call;
|
||||
bool settled_after_check_call;
|
||||
// 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;
|
||||
|
||||
if( !changed_something )
|
||||
{
|
||||
bitasset = ¤t_asset.bitasset_data( db );
|
||||
settled_before_check_call = bitasset->has_settlement(); // whether already force settled
|
||||
}
|
||||
|
||||
bool called_some = db.check_call_orders( current_asset );
|
||||
|
||||
if( !changed_something )
|
||||
{
|
||||
settled_after_check_call = bitasset->has_settlement(); // whether already force settled
|
||||
|
||||
if( settled_before_check_call != settled_after_check_call || called_some )
|
||||
{
|
||||
changed_something = true;
|
||||
wlog( "process_hf_935 changed something" );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void database::process_bitassets()
|
||||
{
|
||||
time_point_sec head_time = head_block_time();
|
||||
uint32_t head_epoch_seconds = head_time.sec_since_epoch();
|
||||
bool after_hf_core_518 = ( head_time >= HARDFORK_CORE_518_TIME ); // clear expired feeds
|
||||
|
||||
const auto update_bitasset = [this,head_time,head_epoch_seconds,after_hf_core_518]( asset_bitasset_data_object &o )
|
||||
{
|
||||
o.force_settled_volume = 0; // Reset all BitAsset force settlement volumes to zero
|
||||
|
||||
// clear expired feeds
|
||||
if( after_hf_core_518 )
|
||||
{
|
||||
const auto &asset = get( o.asset_id );
|
||||
auto flags = asset.options.flags;
|
||||
if ( ( flags & ( witness_fed_asset | committee_fed_asset ) ) &&
|
||||
o.options.feed_lifetime_sec < head_epoch_seconds ) // if smartcoin && check overflow
|
||||
{
|
||||
fc::time_point_sec calculated = head_time - o.options.feed_lifetime_sec;
|
||||
for( auto itr = o.feeds.rbegin(); itr != o.feeds.rend(); ) // loop feeds
|
||||
{
|
||||
auto feed_time = itr->second.first;
|
||||
std::advance( itr, 1 );
|
||||
if( feed_time < calculated )
|
||||
o.feeds.erase( itr.base() ); // delete expired feed
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for( const auto& d : get_index_type<asset_bitasset_data_index>().indices() )
|
||||
{
|
||||
modify( d, update_bitasset );
|
||||
if( d.has_settlement() )
|
||||
process_bids(d);
|
||||
}
|
||||
}
|
||||
|
||||
void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props)
|
||||
{ try {
|
||||
const auto& gpo = get_global_properties();
|
||||
|
|
@ -2195,15 +2542,51 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
|
|||
if( (dgpo.next_maintenance_time < HARDFORK_613_TIME) && (next_maintenance_time >= HARDFORK_613_TIME) )
|
||||
deprecate_annual_members(*this);
|
||||
|
||||
// To reset call_price of all call orders, then match by new rule, for hard fork core-343
|
||||
bool to_update_and_match_call_orders_for_hf_343 = false;
|
||||
if( (dgpo.next_maintenance_time <= HARDFORK_CORE_343_TIME) && (next_maintenance_time > HARDFORK_CORE_343_TIME) )
|
||||
to_update_and_match_call_orders_for_hf_343 = true;
|
||||
|
||||
// Process inconsistent price feeds
|
||||
if( (dgpo.next_maintenance_time <= HARDFORK_CORE_868_890_TIME) && (next_maintenance_time > HARDFORK_CORE_868_890_TIME) )
|
||||
process_hf_868_890( *this, to_update_and_match_call_orders_for_hf_343 );
|
||||
|
||||
// Explicitly call check_call_orders of all markets
|
||||
if( (dgpo.next_maintenance_time <= HARDFORK_CORE_935_TIME) && (next_maintenance_time > HARDFORK_CORE_935_TIME)
|
||||
&& !to_update_and_match_call_orders_for_hf_343 )
|
||||
process_hf_935( *this );
|
||||
|
||||
// To reset call_price of all call orders, then match by new rule, for hard fork core-1270
|
||||
bool to_update_and_match_call_orders_for_hf_1270 = false;
|
||||
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;
|
||||
});
|
||||
|
||||
// Reset all BitAsset force settlement volumes to zero
|
||||
//for( const asset_bitasset_data_object* d : get_index_type<asset_bitasset_data_index>() )
|
||||
for( const auto& d : get_index_type<asset_bitasset_data_index>().indices() )
|
||||
modify( d, [](asset_bitasset_data_object& o) { o.force_settled_volume = 0; });
|
||||
// We need to do it after updated next_maintenance_time, to apply new rules here, for hard fork core-343
|
||||
if( to_update_and_match_call_orders_for_hf_343 )
|
||||
{
|
||||
update_call_orders_hf_343(*this);
|
||||
match_call_orders(*this);
|
||||
}
|
||||
|
||||
// We need to do it after updated next_maintenance_time, to apply new rules here, for hard fork core-1270.
|
||||
if( to_update_and_match_call_orders_for_hf_1270 )
|
||||
{
|
||||
update_call_orders_hf_1270(*this);
|
||||
update_median_feeds(*this);
|
||||
match_call_orders(*this);
|
||||
}
|
||||
|
||||
process_bitassets();
|
||||
|
||||
// Ideally we have to do this after every block but that leads to longer block applicaiton/replay times.
|
||||
// So keep it here as it is not critical. valid_to check ensures
|
||||
// these custom account auths and account roles are not usable.
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -431,6 +431,12 @@ struct get_impacted_account_visitor
|
|||
void operator()( const random_number_store_operation& op ) {
|
||||
_impacted.insert( op.account );
|
||||
}
|
||||
void operator()( const bid_collateral_operation& op ) {
|
||||
_impacted.insert( op.bidder );
|
||||
}
|
||||
void operator()( const execute_bid_operation& op ) {
|
||||
_impacted.insert( op.bidder );
|
||||
}
|
||||
};
|
||||
|
||||
void graphene::chain::operation_get_impacted_accounts( const operation& op, flat_set<account_id_type>& result, bool ignore_custom_operation_required_auths ) {
|
||||
|
|
|
|||
|
|
@ -250,8 +250,40 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s
|
|||
auto settle_price = bitasset.current_feed.settlement_price;
|
||||
if( settle_price.is_null() ) return false; // no feed
|
||||
|
||||
const call_order_index& call_index = get_index_type<call_order_index>();
|
||||
const auto& call_price_index = call_index.indices().get<by_price>();
|
||||
const call_order_object* call_ptr = nullptr; // place holder for the call order with least collateral ratio
|
||||
|
||||
asset_id_type debt_asset_id = mia.id;
|
||||
auto call_min = price::min( bitasset.options.short_backing_asset, debt_asset_id );
|
||||
|
||||
auto maint_time = get_dynamic_global_properties().next_maintenance_time;
|
||||
bool before_core_hardfork_1270 = ( maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue
|
||||
|
||||
if( before_core_hardfork_1270 ) // before core-1270 hard fork, check with call_price
|
||||
{
|
||||
const auto& call_price_index = get_index_type<call_order_index>().indices().get<by_price>();
|
||||
auto call_itr = call_price_index.lower_bound( call_min );
|
||||
if( call_itr == call_price_index.end() ) // no call order
|
||||
return false;
|
||||
call_ptr = &(*call_itr);
|
||||
}
|
||||
else // after core-1270 hard fork, check with collateralization
|
||||
{
|
||||
const auto& call_collateral_index = get_index_type<call_order_index>().indices().get<by_collateral>();
|
||||
auto call_itr = call_collateral_index.lower_bound( call_min );
|
||||
if( call_itr == call_collateral_index.end() ) // no call order
|
||||
return false;
|
||||
call_ptr = &(*call_itr);
|
||||
}
|
||||
if( call_ptr->debt_type() != debt_asset_id ) // no call order
|
||||
return false;
|
||||
|
||||
price highest = settle_price;
|
||||
if( maint_time > HARDFORK_CORE_1270_TIME )
|
||||
// due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here
|
||||
highest = bitasset.current_feed.max_short_squeeze_price();
|
||||
else if( maint_time > HARDFORK_CORE_338_TIME )
|
||||
// due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here
|
||||
highest = bitasset.current_feed.max_short_squeeze_price_before_hf_1270();
|
||||
|
||||
const limit_order_index& limit_index = get_index_type<limit_order_index>();
|
||||
const auto& limit_price_index = limit_index.indices().get<by_price>();
|
||||
|
|
@ -261,38 +293,37 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s
|
|||
// stop when limit orders are selling too little USD for too much CORE
|
||||
auto lowest_possible_bid = price::min( mia.id, bitasset.options.short_backing_asset );
|
||||
|
||||
assert( highest_possible_bid.base.asset_id == lowest_possible_bid.base.asset_id );
|
||||
FC_ASSERT( highest_possible_bid.base.asset_id == lowest_possible_bid.base.asset_id );
|
||||
// NOTE limit_price_index is sorted from greatest to least
|
||||
auto limit_itr = limit_price_index.lower_bound( highest_possible_bid );
|
||||
auto limit_end = limit_price_index.upper_bound( lowest_possible_bid );
|
||||
|
||||
auto call_min = price::min( bitasset.options.short_backing_asset, mia.id );
|
||||
auto call_max = price::max( bitasset.options.short_backing_asset, mia.id );
|
||||
auto call_itr = call_price_index.lower_bound( call_min );
|
||||
auto call_end = call_price_index.upper_bound( call_max );
|
||||
|
||||
if( call_itr == call_end ) return false; // no call orders
|
||||
|
||||
price highest = settle_price;
|
||||
if( limit_itr != limit_end ) {
|
||||
assert( settle_price.base.asset_id == limit_itr->sell_price.base.asset_id );
|
||||
highest = std::max( limit_itr->sell_price, settle_price );
|
||||
FC_ASSERT( highest.base.asset_id == limit_itr->sell_price.base.asset_id );
|
||||
highest = std::max( limit_itr->sell_price, highest );
|
||||
}
|
||||
|
||||
auto least_collateral = call_itr->collateralization();
|
||||
auto least_collateral = call_ptr->collateralization();
|
||||
if( ~least_collateral >= highest )
|
||||
{
|
||||
elog( "Black Swan detected: \n"
|
||||
wdump( (*call_ptr) );
|
||||
elog( "Black Swan detected on asset ${symbol} (${id}) at block ${b}: \n"
|
||||
" Least collateralized call: ${lc} ${~lc}\n"
|
||||
// " Highest Bid: ${hb} ${~hb}\n"
|
||||
" Settle Price: ${sp} ${~sp}\n"
|
||||
" Max: ${h} ${~h}\n",
|
||||
" Settle Price: ${~sp} ${sp}\n"
|
||||
" Max: ${~h} ${h}\n",
|
||||
("id",mia.id)("symbol",mia.symbol)("b",head_block_num())
|
||||
("lc",least_collateral.to_real())("~lc",(~least_collateral).to_real())
|
||||
// ("hb",limit_itr->sell_price.to_real())("~hb",(~limit_itr->sell_price).to_real())
|
||||
("sp",settle_price.to_real())("~sp",(~settle_price).to_real())
|
||||
("h",highest.to_real())("~h",(~highest).to_real()) );
|
||||
edump((enable_black_swan));
|
||||
FC_ASSERT( enable_black_swan, "Black swan was detected during a margin update which is not allowed to trigger a blackswan" );
|
||||
globally_settle_asset(mia, ~least_collateral );
|
||||
if( maint_time > HARDFORK_CORE_338_TIME && ~least_collateral <= settle_price )
|
||||
// globol settle at feed price if possible
|
||||
globally_settle_asset(mia, settle_price );
|
||||
else
|
||||
globally_settle_asset(mia, ~least_collateral );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -300,33 +331,30 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s
|
|||
|
||||
void database::clear_expired_orders()
|
||||
{ try {
|
||||
detail::with_skip_flags( *this,
|
||||
get_node_properties().skip_flags | skip_authority_check, [&](){
|
||||
transaction_evaluation_state cancel_context(this);
|
||||
|
||||
//Cancel expired limit orders
|
||||
auto& limit_index = get_index_type<limit_order_index>().indices().get<by_expiration>();
|
||||
while( !limit_index.empty() && limit_index.begin()->expiration <= head_block_time() )
|
||||
{
|
||||
limit_order_cancel_operation canceler;
|
||||
const limit_order_object& order = *limit_index.begin();
|
||||
canceler.fee_paying_account = order.seller;
|
||||
canceler.order = order.id;
|
||||
canceler.fee = current_fee_schedule().calculate_fee( canceler );
|
||||
if( canceler.fee.amount > order.deferred_fee )
|
||||
{
|
||||
// Cap auto-cancel fees at deferred_fee; see #549
|
||||
wlog( "At block ${b}, fee for clearing expired order ${oid} was capped at deferred_fee ${fee}", ("b", head_block_num())("oid", order.id)("fee", order.deferred_fee) );
|
||||
canceler.fee = asset( order.deferred_fee, asset_id_type() );
|
||||
}
|
||||
// we know the fee for this op is set correctly since it is set by the chain.
|
||||
// this allows us to avoid a hung chain:
|
||||
// - if #549 case above triggers
|
||||
// - if the fee is incorrect, which may happen due to #435 (although since cancel is a fixed-fee op, it shouldn't)
|
||||
cancel_context.skip_fee_schedule_check = true;
|
||||
apply_operation(cancel_context, canceler);
|
||||
}
|
||||
});
|
||||
//Cancel expired limit orders
|
||||
auto head_time = head_block_time();
|
||||
auto maint_time = get_dynamic_global_properties().next_maintenance_time;
|
||||
bool before_core_hardfork_184 = ( maint_time <= HARDFORK_CORE_184_TIME ); // something-for-nothing
|
||||
bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding
|
||||
bool before_core_hardfork_606 = ( maint_time <= HARDFORK_CORE_606_TIME ); // feed always trigger call
|
||||
auto& limit_index = get_index_type<limit_order_index>().indices().get<by_expiration>();
|
||||
while( !limit_index.empty() && limit_index.begin()->expiration <= head_time )
|
||||
{
|
||||
const limit_order_object& order = *limit_index.begin();
|
||||
auto base_asset = order.sell_price.base.asset_id;
|
||||
auto quote_asset = order.sell_price.quote.asset_id;
|
||||
cancel_limit_order( order );
|
||||
if( before_core_hardfork_606 )
|
||||
{
|
||||
// check call orders
|
||||
// Comments below are copied from limit_order_cancel_evaluator::do_apply(...)
|
||||
// Possible optimization: order can be called by cancelling a limit order
|
||||
// if the canceled order was at the top of the book.
|
||||
// Do I need to check calls in both assets?
|
||||
check_call_orders( base_asset( *this ) );
|
||||
check_call_orders( quote_asset( *this ) );
|
||||
}
|
||||
}
|
||||
|
||||
//Process expired force settlement orders
|
||||
auto& settlement_index = get_index_type<force_settlement_index>().indices().get<by_expiration>();
|
||||
|
|
@ -334,9 +362,12 @@ void database::clear_expired_orders()
|
|||
{
|
||||
asset_id_type current_asset = settlement_index.begin()->settlement_asset_id();
|
||||
asset max_settlement_volume;
|
||||
price settlement_fill_price;
|
||||
price settlement_price;
|
||||
bool current_asset_finished = false;
|
||||
bool extra_dump = false;
|
||||
|
||||
auto next_asset = [¤t_asset, &settlement_index, &extra_dump] {
|
||||
auto next_asset = [¤t_asset, ¤t_asset_finished, &settlement_index, &extra_dump] {
|
||||
auto bound = settlement_index.upper_bound(current_asset);
|
||||
if( bound == settlement_index.end() )
|
||||
{
|
||||
|
|
@ -351,6 +382,7 @@ void database::clear_expired_orders()
|
|||
ilog( "next_asset returning true, bound is ${b}", ("b", *bound) );
|
||||
}
|
||||
current_asset = bound->settlement_asset_id();
|
||||
current_asset_finished = false;
|
||||
return true;
|
||||
};
|
||||
|
||||
|
|
@ -379,12 +411,12 @@ void database::clear_expired_orders()
|
|||
if( mia.has_settlement() )
|
||||
{
|
||||
ilog( "Canceling a force settlement because of black swan" );
|
||||
cancel_order( order );
|
||||
cancel_settle_order( order );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Has this order not reached its settlement date?
|
||||
if( order.settlement_date > head_block_time() )
|
||||
if( order.settlement_date > head_time )
|
||||
{
|
||||
if( next_asset() )
|
||||
{
|
||||
|
|
@ -401,12 +433,14 @@ void database::clear_expired_orders()
|
|||
{
|
||||
ilog("Canceling a force settlement in ${asset} because settlement price is null",
|
||||
("asset", mia_object.symbol));
|
||||
cancel_order(order);
|
||||
cancel_settle_order(order);
|
||||
continue;
|
||||
}
|
||||
if( max_settlement_volume.asset_id != current_asset )
|
||||
max_settlement_volume = mia_object.amount(mia.max_force_settlement_volume(mia_object.dynamic_data(*this).current_supply));
|
||||
if( mia.force_settled_volume >= max_settlement_volume.amount )
|
||||
// When current_asset_finished is true, this would be the 2nd time processing the same order.
|
||||
// In this case, we move to the next asset.
|
||||
if( mia.force_settled_volume >= max_settlement_volume.amount || current_asset_finished )
|
||||
{
|
||||
/*
|
||||
ilog("Skipping force settlement in ${asset}; settled ${settled_volume} / ${max_volume}",
|
||||
|
|
@ -424,13 +458,23 @@ void database::clear_expired_orders()
|
|||
break;
|
||||
}
|
||||
|
||||
auto& pays = order.balance;
|
||||
auto receives = (order.balance * mia.current_feed.settlement_price);
|
||||
receives.amount = (fc::uint128_t(receives.amount.value) *
|
||||
(GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent) / GRAPHENE_100_PERCENT).to_uint64();
|
||||
assert(receives <= order.balance * mia.current_feed.settlement_price);
|
||||
if( settlement_fill_price.base.asset_id != current_asset )
|
||||
settlement_fill_price = mia.current_feed.settlement_price
|
||||
/ ratio_type( GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent,
|
||||
GRAPHENE_100_PERCENT );
|
||||
|
||||
price settlement_price = pays / receives;
|
||||
if( before_core_hardfork_342 )
|
||||
{
|
||||
auto& pays = order.balance;
|
||||
auto receives = (order.balance * mia.current_feed.settlement_price);
|
||||
receives.amount = ( fc::uint128_t(receives.amount.value) *
|
||||
(GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent) /
|
||||
GRAPHENE_100_PERCENT ).to_uint64();
|
||||
assert(receives <= order.balance * mia.current_feed.settlement_price);
|
||||
settlement_price = pays / receives;
|
||||
}
|
||||
else if( settlement_price.base.asset_id != current_asset ) // only calculate once per asset
|
||||
settlement_price = settlement_fill_price;
|
||||
|
||||
auto& call_index = get_index_type<call_order_index>().indices().get<by_collateral>();
|
||||
asset settled = mia_object.amount(mia.force_settled_volume);
|
||||
|
|
@ -446,15 +490,33 @@ void database::clear_expired_orders()
|
|||
if( order.balance.amount == 0 )
|
||||
{
|
||||
wlog( "0 settlement detected" );
|
||||
cancel_order( order );
|
||||
cancel_settle_order( order );
|
||||
break;
|
||||
}
|
||||
try {
|
||||
settled += match(*itr, order, settlement_price, max_settlement);
|
||||
asset new_settled = match(*itr, order, settlement_price, max_settlement, settlement_fill_price);
|
||||
if( !before_core_hardfork_184 && new_settled.amount == 0 ) // unable to fill this settle order
|
||||
{
|
||||
if( find_object( order_id ) ) // the settle order hasn't been cancelled
|
||||
current_asset_finished = true;
|
||||
break;
|
||||
}
|
||||
settled += new_settled;
|
||||
// before hard fork core-342, if new_settled > 0, we'll have:
|
||||
// * call order is completely filled (thus itr will change in next loop), or
|
||||
// * settle order is completely filled (thus find_object(order_id) will be false so will break out), or
|
||||
// * reached max_settlement_volume limit (thus new_settled == max_settlement so will break out).
|
||||
//
|
||||
// after hard fork core-342, if new_settled > 0, we'll have:
|
||||
// * call order is completely filled (thus itr will change in next loop), or
|
||||
// * settle order is completely filled (thus find_object(order_id) will be false so will break out), or
|
||||
// * reached max_settlement_volume limit, but it's possible that new_settled < max_settlement,
|
||||
// in this case, new_settled will be zero in next iteration of the loop, so no need to check here.
|
||||
}
|
||||
catch ( const black_swan_exception& e ) {
|
||||
wlog( "black swan detected: ${e}", ("e", e.to_detail_string() ) );
|
||||
cancel_order( order );
|
||||
wlog( "Cancelling a settle_order since it may trigger a black swan: ${o}, ${e}",
|
||||
("o", order)("e", e.to_detail_string()) );
|
||||
cancel_settle_order( order );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -471,6 +533,7 @@ void database::clear_expired_orders()
|
|||
void database::update_expired_feeds()
|
||||
{
|
||||
const auto head_time = head_block_time();
|
||||
const auto next_maint_time = get_dynamic_global_properties().next_maintenance_time;
|
||||
bool after_hardfork_615 = ( head_time >= HARDFORK_615_TIME );
|
||||
|
||||
const auto& idx = get_index_type<asset_bitasset_data_index>().indices().get<by_feed_expiration>();
|
||||
|
|
@ -485,9 +548,9 @@ void database::update_expired_feeds()
|
|||
if( after_hardfork_615 || b.feed_is_expired_before_hardfork_615( head_time ) )
|
||||
{
|
||||
auto old_median_feed = b.current_feed;
|
||||
modify( b, [head_time,&update_cer]( asset_bitasset_data_object& abdo )
|
||||
modify( b, [head_time,next_maint_time,&update_cer]( asset_bitasset_data_object& abdo )
|
||||
{
|
||||
abdo.update_median_feeds( head_time );
|
||||
abdo.update_median_feeds( head_time, next_maint_time );
|
||||
if( abdo.need_to_update_cer() )
|
||||
{
|
||||
update_cer = true;
|
||||
|
|
|
|||
4
libraries/chain/hardfork.d/CORE_1270.hf
Normal file
4
libraries/chain/hardfork.d/CORE_1270.hf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// bitshares-core issue #1270 Call price is inconsistent when MCR changed
|
||||
#ifndef HARDFORK_CORE_1270_TIME
|
||||
#define HARDFORK_CORE_1270_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
|
||||
#endif
|
||||
4
libraries/chain/hardfork.d/CORE_1465.hf
Normal file
4
libraries/chain/hardfork.d/CORE_1465.hf
Normal 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
|
||||
4
libraries/chain/hardfork.d/CORE_1479.hf
Normal file
4
libraries/chain/hardfork.d/CORE_1479.hf
Normal 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
|
||||
4
libraries/chain/hardfork.d/CORE_1573.hf
Normal file
4
libraries/chain/hardfork.d/CORE_1573.hf
Normal 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
|
||||
4
libraries/chain/hardfork.d/CORE_1669.hf
Normal file
4
libraries/chain/hardfork.d/CORE_1669.hf
Normal 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
|
||||
4
libraries/chain/hardfork.d/CORE_1692.hf
Normal file
4
libraries/chain/hardfork.d/CORE_1692.hf
Normal 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
|
||||
4
libraries/chain/hardfork.d/CORE_184.hf
Normal file
4
libraries/chain/hardfork.d/CORE_184.hf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// bitshares-core issue #184 Fix "Potential something-for-nothing fill bug"
|
||||
#ifndef HARDFORK_CORE_184_TIME
|
||||
#define HARDFORK_CORE_184_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
|
||||
#endif
|
||||
4
libraries/chain/hardfork.d/CORE_216.hf
Normal file
4
libraries/chain/hardfork.d/CORE_216.hf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// bitshares-core #216 Process to reset a Smartcoin after a Black Swan
|
||||
#ifndef HARDFORK_CORE_216_TIME
|
||||
#define HARDFORK_CORE_216_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
|
||||
#endif
|
||||
4
libraries/chain/hardfork.d/CORE_338.hf
Normal file
4
libraries/chain/hardfork.d/CORE_338.hf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// bitshares-core issue #338 Fix "margin call order fills at price of matching limit_order"
|
||||
#ifndef HARDFORK_CORE_338_TIME
|
||||
#define HARDFORK_CORE_338_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
|
||||
#endif
|
||||
5
libraries/chain/hardfork.d/CORE_342.hf
Normal file
5
libraries/chain/hardfork.d/CORE_342.hf
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// bitshares-core issue #342
|
||||
// Mitigate rounding issue when matching orders
|
||||
#ifndef HARDFORK_CORE_342_TIME
|
||||
#define HARDFORK_CORE_342_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
|
||||
#endif
|
||||
5
libraries/chain/hardfork.d/CORE_343.hf
Normal file
5
libraries/chain/hardfork.d/CORE_343.hf
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// bitshares-core issue #343
|
||||
// Fix "Inconsistent sorting of call orders between matching against a limit order and a force settle order"
|
||||
#ifndef HARDFORK_CORE_343_TIME
|
||||
#define HARDFORK_CORE_343_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
|
||||
#endif
|
||||
4
libraries/chain/hardfork.d/CORE_453.hf
Normal file
4
libraries/chain/hardfork.d/CORE_453.hf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// bitshares-core issue #453 Fix "Multiple limit order and call order matching issue"
|
||||
#ifndef HARDFORK_CORE_453_TIME
|
||||
#define HARDFORK_CORE_453_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
|
||||
#endif
|
||||
4
libraries/chain/hardfork.d/CORE_460.hf
Normal file
4
libraries/chain/hardfork.d/CORE_460.hf
Normal 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
|
||||
4
libraries/chain/hardfork.d/CORE_518.hf
Normal file
4
libraries/chain/hardfork.d/CORE_518.hf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// bitshares-core issue #518 Clean up bitasset_data during maintenance
|
||||
#ifndef HARDFORK_CORE_518_TIME
|
||||
#define HARDFORK_CORE_518_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
|
||||
#endif
|
||||
4
libraries/chain/hardfork.d/CORE_583.hf
Normal file
4
libraries/chain/hardfork.d/CORE_583.hf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// bitshares-core issue #583 Always allow updating a call order to higher collateral ratio
|
||||
#ifndef HARDFORK_CORE_583_TIME
|
||||
#define HARDFORK_CORE_583_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
|
||||
#endif
|
||||
5
libraries/chain/hardfork.d/CORE_604.hf
Normal file
5
libraries/chain/hardfork.d/CORE_604.hf
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// bitshares-core issue #604
|
||||
// Implement BSIP 26: refund order creation fee in original paid asset when order is cancelled
|
||||
#ifndef HARDFORK_CORE_604_TIME
|
||||
#define HARDFORK_CORE_604_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
|
||||
#endif
|
||||
4
libraries/chain/hardfork.d/CORE_606.hf
Normal file
4
libraries/chain/hardfork.d/CORE_606.hf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// bitshares-core issue #606 Fix "Undercollateralized short positions should be called regardless of asks"
|
||||
#ifndef HARDFORK_CORE_606_TIME
|
||||
#define HARDFORK_CORE_606_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
|
||||
#endif
|
||||
4
libraries/chain/hardfork.d/CORE_625.hf
Normal file
4
libraries/chain/hardfork.d/CORE_625.hf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// bitshares-core issue #625 Fix "Potential erratic order matching issue involving margin call orders"
|
||||
#ifndef HARDFORK_CORE_625_TIME
|
||||
#define HARDFORK_CORE_625_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
|
||||
#endif
|
||||
4
libraries/chain/hardfork.d/CORE_834.hf
Normal file
4
libraries/chain/hardfork.d/CORE_834.hf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// bitshares-core issue #834 "BSIP38: add target CR option to short positions"
|
||||
#ifndef HARDFORK_CORE_834_TIME
|
||||
#define HARDFORK_CORE_834_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
|
||||
#endif
|
||||
5
libraries/chain/hardfork.d/CORE_868_890.hf
Normal file
5
libraries/chain/hardfork.d/CORE_868_890.hf
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// bitshares-core issue #868 Clear price feed data after updated a bitAsset's backing asset ID
|
||||
// bitshares-core issue #890 Update median feeds after feed_lifetime_sec changed
|
||||
#ifndef HARDFORK_CORE_868_890_TIME
|
||||
#define HARDFORK_CORE_868_890_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
|
||||
#endif
|
||||
5
libraries/chain/hardfork.d/CORE_922_931.hf
Normal file
5
libraries/chain/hardfork.d/CORE_922_931.hf
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// bitshares-core issue #922 Missing checks when updating an asset's bitasset_data
|
||||
// bitshares-core issue #931 Changing backing asset ID runs some checks against the old value instead of the new
|
||||
#ifndef HARDFORK_CORE_922_931_TIME
|
||||
#define HARDFORK_CORE_922_931_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
|
||||
#endif
|
||||
4
libraries/chain/hardfork.d/CORE_935.hf
Normal file
4
libraries/chain/hardfork.d/CORE_935.hf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// bitshares-core issue #935 Call check_call_orders not only when settlement_price changed
|
||||
#ifndef HARDFORK_CORE_935_TIME
|
||||
#define HARDFORK_CORE_935_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
|
||||
#endif
|
||||
|
|
@ -103,6 +103,7 @@ namespace graphene { namespace chain {
|
|||
void_result do_apply( const asset_update_bitasset_operation& o );
|
||||
|
||||
const asset_bitasset_data_object* bitasset_to_update = nullptr;
|
||||
const asset_object* asset_to_update = nullptr;
|
||||
};
|
||||
|
||||
class asset_update_dividend_evaluator : public evaluator<asset_update_dividend_evaluator>
|
||||
|
|
|
|||
|
|
@ -209,6 +209,9 @@ namespace graphene { namespace chain {
|
|||
price_feed current_feed;
|
||||
/// This is the publication time of the oldest feed which was factored into current_feed.
|
||||
time_point_sec current_feed_publication_time;
|
||||
/// Call orders with collateralization (aka collateral/debt) not greater than this value are in margin call territory.
|
||||
/// This value is derived from @ref current_feed for better performance and should be kept consistent.
|
||||
price current_maintenance_collateralization;
|
||||
|
||||
/// True if this asset implements a @ref prediction_market
|
||||
bool is_prediction_market = false;
|
||||
|
|
@ -260,7 +263,19 @@ namespace graphene { namespace chain {
|
|||
{ return feed_expiration_time() >= current_time; }
|
||||
bool feed_is_expired(time_point_sec current_time)const
|
||||
{ return feed_expiration_time() <= current_time; }
|
||||
void update_median_feeds(time_point_sec current_time);
|
||||
|
||||
/******
|
||||
* @brief calculate the median feed
|
||||
*
|
||||
* This calculates the median feed from @ref feeds, feed_lifetime_sec
|
||||
* in @ref options, and the given parameters.
|
||||
* It may update the current_feed_publication_time, current_feed and
|
||||
* current_maintenance_collateralization member variables.
|
||||
*
|
||||
* @param current_time the current time to use in the calculations
|
||||
* @param next_maintenance_time the next chain maintenance time
|
||||
*/
|
||||
void update_median_feeds(time_point_sec current_time, time_point_sec next_maintenance_time);
|
||||
};
|
||||
|
||||
// key extractor for short backing asset
|
||||
|
|
@ -521,6 +536,7 @@ FC_REFLECT_DERIVED( graphene::chain::asset_bitasset_data_object, (graphene::db::
|
|||
(feeds)
|
||||
(current_feed)
|
||||
(current_feed_publication_time)
|
||||
(current_maintenance_collateralization)
|
||||
(options)
|
||||
(force_settled_volume)
|
||||
(is_prediction_market)
|
||||
|
|
|
|||
|
|
@ -407,8 +407,19 @@ namespace graphene { namespace chain {
|
|||
|
||||
/// @{ @group Market Helpers
|
||||
void globally_settle_asset( const asset_object& bitasset, const price& settle_price );
|
||||
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_settle_order(const force_settlement_object& order, bool create_virtual_op = true);
|
||||
void cancel_limit_order(const limit_order_object& order, bool create_virtual_op = true, bool skip_cancel_fee = false);
|
||||
void revive_bitasset( const asset_object& bitasset );
|
||||
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
|
||||
|
|
@ -418,27 +429,30 @@ namespace graphene { namespace chain {
|
|||
* 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_before_hardfork_625(const limit_order_object& new_order_object, bool allow_black_swan = true);
|
||||
bool apply_order(const limit_order_object& new_order_object, bool allow_black_swan = true);
|
||||
|
||||
/**
|
||||
* Matches the two orders,
|
||||
* Matches the two orders, the first parameter is taker, the second is maker.
|
||||
*
|
||||
* @return a bit field indicating which orders were filled (and thus removed)
|
||||
*
|
||||
* 0 - no orders were matched
|
||||
* 1 - bid was filled
|
||||
* 2 - ask was filled
|
||||
* 1 - taker was filled
|
||||
* 2 - maker was filled
|
||||
* 3 - both were filled
|
||||
*/
|
||||
///@{
|
||||
template<typename OrderType>
|
||||
int match( const limit_order_object& bid, const OrderType& ask, const price& match_price );
|
||||
int match( const limit_order_object& bid, const limit_order_object& ask, const price& trade_price );
|
||||
int match( const limit_order_object& taker, const limit_order_object& maker, const price& trade_price );
|
||||
int match( const limit_order_object& taker, const call_order_object& maker, const price& trade_price,
|
||||
const price& feed_price, const uint16_t maintenance_collateral_ratio,
|
||||
const optional<price>& maintenance_collateralization );
|
||||
/// @return the amount of asset settled
|
||||
asset match(const call_order_object& call,
|
||||
const force_settlement_object& settle,
|
||||
const price& match_price,
|
||||
asset max_settlement);
|
||||
asset max_settlement,
|
||||
const price& fill_price);
|
||||
///@}
|
||||
|
||||
//////////////////// db_bet.cpp ////////////////////
|
||||
|
|
@ -467,9 +481,12 @@ namespace graphene { namespace chain {
|
|||
/**
|
||||
* @return true if the order was completely filled and thus freed.
|
||||
*/
|
||||
bool fill_order( const limit_order_object& order, const asset& pays, const asset& receives, bool cull_if_small );
|
||||
bool fill_order( const call_order_object& order, const asset& pays, const asset& receives );
|
||||
bool fill_order( const force_settlement_object& settle, const asset& pays, const asset& receives );
|
||||
bool fill_limit_order( const limit_order_object& order, const asset& pays, const asset& receives, bool cull_if_small,
|
||||
const price& fill_price, const bool is_maker );
|
||||
bool fill_call_order( const call_order_object& order, const asset& pays, const asset& receives,
|
||||
const price& fill_price, const bool is_maker );
|
||||
bool fill_settle_order( const force_settlement_object& settle, const asset& pays, const asset& receives,
|
||||
const price& fill_price, const bool is_maker );
|
||||
|
||||
bool check_call_orders( const asset_object& mia, bool enable_black_swan = true, bool for_new_limit_order = false,
|
||||
const asset_bitasset_data_object* bitasset_ptr = nullptr );
|
||||
|
|
@ -522,6 +539,7 @@ namespace graphene { namespace chain {
|
|||
private:
|
||||
void _apply_block( const signed_block& next_block );
|
||||
processed_transaction _apply_transaction( const signed_transaction& trx );
|
||||
void _cancel_bids_and_revive_mpa( const asset_object& bitasset, const asset_bitasset_data_object& bad );
|
||||
|
||||
///Steps involved in applying a new block
|
||||
///@{
|
||||
|
|
@ -574,6 +592,8 @@ namespace graphene { namespace chain {
|
|||
void update_son_statuses( const vector<son_info>& cur_active_sons, const vector<son_info>& new_active_sons );
|
||||
void update_son_wallet( const vector<son_info>& new_active_sons );
|
||||
void update_worker_votes();
|
||||
void process_bids( const asset_bitasset_data_object& bad );
|
||||
void process_bitassets();
|
||||
|
||||
public:
|
||||
double calculate_vesting_factor(const account_object& stake_account);
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ namespace graphene { namespace chain {
|
|||
*
|
||||
* Rather than returning a value, this method fills in core_fee_paid field.
|
||||
*/
|
||||
void convert_fee();
|
||||
virtual void convert_fee();
|
||||
|
||||
object_id_type get_relative_id( object_id_type rel_id )const;
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ namespace graphene { namespace chain {
|
|||
class asset_object;
|
||||
class asset_bitasset_data_object;
|
||||
class call_order_object;
|
||||
struct bid_collateral_operation;
|
||||
struct call_order_update_operation;
|
||||
struct limit_order_cancel_operation;
|
||||
struct limit_order_create_operation;
|
||||
|
|
@ -45,12 +46,17 @@ namespace graphene { namespace chain {
|
|||
|
||||
asset calculate_market_fee( const asset_object* aobj, const asset& trade_amount );
|
||||
|
||||
/** override the default behavior defined by generic_evalautor
|
||||
*/
|
||||
virtual void convert_fee() override;
|
||||
|
||||
/** override the default behavior defined by generic_evalautor which is to
|
||||
* post the fee to fee_paying_account_stats.pending_fees
|
||||
*/
|
||||
virtual void pay_fee() override;
|
||||
|
||||
share_type _deferred_fee = 0;
|
||||
asset _deferred_paid_fee;
|
||||
const limit_order_create_operation* _op = nullptr;
|
||||
const account_object* _seller = nullptr;
|
||||
const asset_object* _sell_asset = nullptr;
|
||||
|
|
@ -81,6 +87,21 @@ 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>
|
||||
{
|
||||
public:
|
||||
typedef bid_collateral_operation operation_type;
|
||||
|
||||
void_result do_evaluate( const bid_collateral_operation& o );
|
||||
void_result do_apply( const bid_collateral_operation& o );
|
||||
|
||||
const asset_object* _debt_asset = nullptr;
|
||||
const asset_bitasset_data_object* _bitasset_data = nullptr;
|
||||
const account_object* _paying_account = nullptr;
|
||||
const collateral_bid_object* _bid = nullptr;
|
||||
};
|
||||
|
||||
} } // graphene::chain
|
||||
|
|
|
|||
|
|
@ -52,7 +52,8 @@ class limit_order_object : public abstract_object<limit_order_object>
|
|||
account_id_type seller;
|
||||
share_type for_sale; ///< asset id is sell_price.base.asset_id
|
||||
price sell_price;
|
||||
share_type deferred_fee;
|
||||
share_type deferred_fee; ///< fee converted to CORE
|
||||
asset deferred_paid_fee; ///< originally paid fee
|
||||
|
||||
pair<asset_id_type,asset_id_type> get_market()const
|
||||
{
|
||||
|
|
@ -63,6 +64,8 @@ class limit_order_object : public abstract_object<limit_order_object>
|
|||
|
||||
asset amount_for_sale()const { return asset( for_sale, sell_price.base.asset_id ); }
|
||||
asset amount_to_receive()const { return amount_for_sale() * sell_price; }
|
||||
asset_id_type sell_asset_id()const { return sell_price.base.asset_id; }
|
||||
asset_id_type receive_asset_id()const { return sell_price.quote.asset_id; }
|
||||
};
|
||||
|
||||
struct by_id;
|
||||
|
|
@ -114,12 +117,15 @@ class call_order_object : public abstract_object<call_order_object>
|
|||
asset get_debt()const { return asset( debt, debt_type() ); }
|
||||
asset amount_to_receive()const { return get_debt(); }
|
||||
asset_id_type debt_type()const { return call_price.quote.asset_id; }
|
||||
asset_id_type collateral_type()const { return call_price.base.asset_id; }
|
||||
price collateralization()const { return get_collateral() / get_debt(); }
|
||||
|
||||
account_id_type borrower;
|
||||
share_type collateral; ///< call_price.base.asset_id, access via get_collateral
|
||||
share_type debt; ///< call_price.quote.asset_id, access via get_collateral
|
||||
price call_price; ///< Debt / Collateral
|
||||
share_type debt; ///< call_price.quote.asset_id, access via get_debt
|
||||
price call_price; ///< Collateral / Debt
|
||||
|
||||
optional<uint16_t> target_collateral_ratio; ///< maximum CR to maintain when selling collateral on margin call
|
||||
|
||||
pair<asset_id_type,asset_id_type> get_market()const
|
||||
{
|
||||
|
|
@ -127,6 +133,20 @@ class call_order_object : public abstract_object<call_order_object>
|
|||
if( tmp.first > tmp.second ) std::swap( tmp.first, tmp.second );
|
||||
return tmp;
|
||||
}
|
||||
/**
|
||||
* Calculate maximum quantity of debt to cover to satisfy @ref target_collateral_ratio.
|
||||
*
|
||||
* @param match_price the matching price if this call order is margin called
|
||||
* @param feed_price median settlement price of debt asset
|
||||
* @param maintenance_collateral_ratio median maintenance collateral ratio of debt asset
|
||||
* @param maintenance_collateralization maintenance collateralization of debt asset,
|
||||
* should only be valid after core-1270 hard fork
|
||||
* @return maximum amount of debt that can be called
|
||||
*/
|
||||
share_type get_max_debt_to_cover( price match_price,
|
||||
price feed_price,
|
||||
const uint16_t maintenance_collateral_ratio,
|
||||
const optional<price>& maintenance_collateralization = optional<price>() )const;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -149,6 +169,27 @@ class force_settlement_object : public abstract_object<force_settlement_object>
|
|||
{ return balance.asset_id; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @class collateral_bid_object
|
||||
* @brief bids of collateral for debt after a black swan
|
||||
*
|
||||
* There should only be one collateral_bid_object per asset per account, and
|
||||
* only for smartcoin assets that have a global settlement_price.
|
||||
*/
|
||||
class collateral_bid_object : public abstract_object<collateral_bid_object>
|
||||
{
|
||||
public:
|
||||
static const uint8_t space_id = implementation_ids;
|
||||
static const uint8_t type_id = impl_collateral_bid_object_type;
|
||||
|
||||
asset get_additional_collateral()const { return inv_swan_price.base; }
|
||||
asset get_debt_covered()const { return inv_swan_price.quote; }
|
||||
asset_id_type debt_type()const { return inv_swan_price.quote.asset_id; }
|
||||
|
||||
account_id_type bidder;
|
||||
price inv_swan_price; // Collateral / Debt
|
||||
};
|
||||
|
||||
struct by_collateral;
|
||||
struct by_account;
|
||||
struct by_price;
|
||||
|
|
@ -200,24 +241,50 @@ typedef multi_index_container<
|
|||
>
|
||||
> force_settlement_object_multi_index_type;
|
||||
|
||||
typedef multi_index_container<
|
||||
collateral_bid_object,
|
||||
indexed_by<
|
||||
ordered_unique< tag<by_id>,
|
||||
member< object, object_id_type, &object::id > >,
|
||||
ordered_unique< tag<by_account>,
|
||||
composite_key< collateral_bid_object,
|
||||
const_mem_fun< collateral_bid_object, asset_id_type, &collateral_bid_object::debt_type>,
|
||||
member< collateral_bid_object, account_id_type, &collateral_bid_object::bidder>
|
||||
>
|
||||
>,
|
||||
ordered_unique< tag<by_price>,
|
||||
composite_key< collateral_bid_object,
|
||||
const_mem_fun< collateral_bid_object, asset_id_type, &collateral_bid_object::debt_type>,
|
||||
member< collateral_bid_object, price, &collateral_bid_object::inv_swan_price >,
|
||||
member< object, object_id_type, &object::id >
|
||||
>,
|
||||
composite_key_compare< std::less<asset_id_type>, std::greater<price>, std::less<object_id_type> >
|
||||
>
|
||||
>
|
||||
> collateral_bid_object_multi_index_type;
|
||||
|
||||
typedef generic_index<call_order_object, call_order_multi_index_type> call_order_index;
|
||||
typedef generic_index<force_settlement_object, force_settlement_object_multi_index_type> force_settlement_index;
|
||||
typedef generic_index<collateral_bid_object, collateral_bid_object_multi_index_type> collateral_bid_index;
|
||||
|
||||
} } // graphene::chain
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::limit_order_object,
|
||||
(graphene::db::object),
|
||||
(expiration)(seller)(for_sale)(sell_price)(deferred_fee)
|
||||
(expiration)(seller)(for_sale)(sell_price)(deferred_fee)(deferred_paid_fee)
|
||||
)
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::call_order_object, (graphene::db::object),
|
||||
(borrower)(collateral)(debt)(call_price) )
|
||||
(borrower)(collateral)(debt)(call_price)(target_collateral_ratio) )
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::force_settlement_object,
|
||||
(graphene::db::object),
|
||||
(owner)(balance)(settlement_date)
|
||||
)
|
||||
FC_REFLECT_DERIVED( graphene::chain::collateral_bid_object, (graphene::db::object),
|
||||
(bidder)(inv_swan_price) )
|
||||
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::limit_order_object )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::call_order_object )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::force_settlement_object )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::collateral_bid_object )
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ namespace graphene { namespace chain {
|
|||
|
||||
extern const int64_t scaled_precision_lut[];
|
||||
|
||||
struct price;
|
||||
|
||||
struct asset
|
||||
{
|
||||
asset( share_type a = 0, asset_id_type id = asset_id_type() )
|
||||
|
|
@ -94,6 +96,8 @@ namespace graphene { namespace chain {
|
|||
FC_ASSERT( precision < 19 );
|
||||
return scaled_precision_lut[ precision ];
|
||||
}
|
||||
|
||||
asset multiply_and_round_up( const price& p )const; ///< Multiply and round up
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -146,6 +150,14 @@ namespace graphene { namespace chain {
|
|||
bool operator != ( const price& a, const price& b );
|
||||
asset operator * ( const asset& a, const price& b );
|
||||
|
||||
price operator * ( const price& p, const ratio_type& r );
|
||||
price operator / ( const price& p, const ratio_type& r );
|
||||
|
||||
inline price& operator *= ( price& p, const ratio_type& r )
|
||||
{ return p = p * r; }
|
||||
inline price& operator /= ( price& p, const ratio_type& r )
|
||||
{ return p = p / r; }
|
||||
|
||||
/**
|
||||
* @class price_feed
|
||||
* @brief defines market parameters for margin positions
|
||||
|
|
@ -181,15 +193,6 @@ namespace graphene { namespace chain {
|
|||
/** Fixed point between 1.000 and 10.000, implied fixed point denominator is GRAPHENE_COLLATERAL_RATIO_DENOM */
|
||||
uint16_t maximum_short_squeeze_ratio = GRAPHENE_DEFAULT_MAX_SHORT_SQUEEZE_RATIO;
|
||||
|
||||
/**
|
||||
* When updating a call order the following condition must be maintained:
|
||||
*
|
||||
* debt * maintenance_price() < collateral
|
||||
* debt * settlement_price < debt * maintenance
|
||||
* debt * maintenance_price() < debt * max_short_squeeze_price()
|
||||
price maintenance_price()const;
|
||||
*/
|
||||
|
||||
/** When selling collateral to pay off debt, the least amount of debt to receive should be
|
||||
* min_usd = max_short_squeeze_price() * collateral
|
||||
*
|
||||
|
|
@ -197,6 +200,13 @@ namespace graphene { namespace chain {
|
|||
* must be confirmed by having the max_short_squeeze_price() move below the black swan price.
|
||||
*/
|
||||
price max_short_squeeze_price()const;
|
||||
/// Another implementation of max_short_squeeze_price() before the core-1270 hard fork
|
||||
price max_short_squeeze_price_before_hf_1270()const;
|
||||
|
||||
/// Call orders with collateralization (aka collateral/debt) not greater than this value are in margin call territory.
|
||||
/// Calculation: ~settlement_price * maintenance_collateral_ratio / GRAPHENE_COLLATERAL_RATIO_DENOM
|
||||
price maintenance_collateralization()const;
|
||||
|
||||
///@}
|
||||
|
||||
friend bool operator == ( const price_feed& a, const price_feed& b )
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#pragma once
|
||||
#include <graphene/chain/protocol/base.hpp>
|
||||
#include <graphene/chain/protocol/asset.hpp>
|
||||
#include <graphene/chain/protocol/ext.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
|
|
@ -111,6 +112,15 @@ namespace graphene { namespace chain {
|
|||
*/
|
||||
struct call_order_update_operation : public base_operation
|
||||
{
|
||||
/**
|
||||
* Options to be used in @ref call_order_update_operation.
|
||||
*
|
||||
* @note this struct can be expanded by adding more options in the end.
|
||||
*/
|
||||
struct options_type
|
||||
{
|
||||
optional<uint16_t> target_collateral_ratio; ///< maximum CR to maintain when selling collateral on margin call
|
||||
};
|
||||
/** this is slightly more expensive than limit orders, this pricing impacts prediction markets */
|
||||
struct fee_parameters_type { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; };
|
||||
|
||||
|
|
@ -118,6 +128,7 @@ namespace graphene { namespace chain {
|
|||
account_id_type funding_account; ///< pays fee, collateral, and cover
|
||||
asset delta_collateral; ///< the amount of collateral to add to the margin position
|
||||
asset delta_debt; ///< the amount of the debt to be paid off, may be negative to issue new debt
|
||||
typedef extension<options_type> extensions_type; // note: this will be jsonified to {...} but no longer [...]
|
||||
extensions_type extensions;
|
||||
|
||||
account_id_type fee_payer()const { return funding_account; }
|
||||
|
|
@ -136,14 +147,16 @@ namespace graphene { namespace chain {
|
|||
struct fee_parameters_type {};
|
||||
|
||||
fill_order_operation(){}
|
||||
fill_order_operation( object_id_type o, account_id_type a, asset p, asset r, asset f )
|
||||
:order_id(o),account_id(a),pays(p),receives(r),fee(f){}
|
||||
fill_order_operation( object_id_type o, account_id_type a, asset p, asset r, asset f, price fp, bool m )
|
||||
:order_id(o),account_id(a),pays(p),receives(r),fee(f),fill_price(fp),is_maker(m) {}
|
||||
|
||||
object_id_type order_id;
|
||||
account_id_type account_id;
|
||||
asset pays;
|
||||
asset receives;
|
||||
asset fee; // paid by receiving account
|
||||
price fill_price;
|
||||
bool is_maker;
|
||||
|
||||
|
||||
pair<asset_id_type,asset_id_type> get_market()const
|
||||
|
|
@ -159,22 +172,80 @@ namespace graphene { namespace chain {
|
|||
share_type calculate_fee(const fee_parameters_type& k)const { return 0; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup operations
|
||||
*
|
||||
* This operation can be used after a black swan to bid collateral for
|
||||
* taking over part of the debt and the settlement_fund (see BSIP-0018).
|
||||
*/
|
||||
struct bid_collateral_operation : public base_operation
|
||||
{
|
||||
/** should be equivalent to call_order_update fee */
|
||||
struct fee_parameters_type { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; };
|
||||
|
||||
asset fee;
|
||||
account_id_type bidder; ///< pays fee and additional collateral
|
||||
asset additional_collateral; ///< the amount of collateral to bid for the debt
|
||||
asset debt_covered; ///< the amount of debt to take over
|
||||
extensions_type extensions;
|
||||
|
||||
account_id_type fee_payer()const { return bidder; }
|
||||
void validate()const;
|
||||
share_type calculate_fee(const fee_parameters_type& k)const { return k.fee; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup operations
|
||||
*
|
||||
* @note This is a virtual operation that is created while reviving a
|
||||
* bitasset from collateral bids.
|
||||
*/
|
||||
struct execute_bid_operation : public base_operation
|
||||
{
|
||||
struct fee_parameters_type {};
|
||||
|
||||
execute_bid_operation(){}
|
||||
execute_bid_operation( account_id_type a, asset d, asset c )
|
||||
: bidder(a), debt(d), collateral(c) {}
|
||||
|
||||
asset fee;
|
||||
account_id_type bidder;
|
||||
asset debt;
|
||||
asset collateral;
|
||||
|
||||
account_id_type fee_payer()const { return bidder; }
|
||||
void validate()const { FC_ASSERT( !"virtual operation" ); }
|
||||
|
||||
/// This is a virtual operation; there is no fee
|
||||
share_type calculate_fee(const fee_parameters_type& k)const { return 0; }
|
||||
};
|
||||
|
||||
} } // graphene::chain
|
||||
|
||||
FC_REFLECT( graphene::chain::limit_order_create_operation::fee_parameters_type, (fee) )
|
||||
FC_REFLECT( graphene::chain::limit_order_cancel_operation::fee_parameters_type, (fee) )
|
||||
FC_REFLECT( graphene::chain::call_order_update_operation::fee_parameters_type, (fee) )
|
||||
FC_REFLECT( graphene::chain::bid_collateral_operation::fee_parameters_type, (fee) )
|
||||
/// THIS IS THE ONLY VIRTUAL OPERATION THUS FAR...
|
||||
FC_REFLECT( graphene::chain::fill_order_operation::fee_parameters_type, )
|
||||
FC_REFLECT( graphene::chain::fill_order_operation::fee_parameters_type, ) // VIRTUAL
|
||||
FC_REFLECT( graphene::chain::execute_bid_operation::fee_parameters_type, ) // VIRTUAL
|
||||
FC_REFLECT( graphene::chain::call_order_update_operation::options_type, (target_collateral_ratio) )
|
||||
FC_REFLECT( graphene::chain::limit_order_create_operation,(fee)(seller)(amount_to_sell)(min_to_receive)(expiration)(fill_or_kill)(extensions))
|
||||
FC_REFLECT( graphene::chain::limit_order_cancel_operation,(fee)(fee_paying_account)(order)(extensions) )
|
||||
FC_REFLECT( graphene::chain::call_order_update_operation, (fee)(funding_account)(delta_collateral)(delta_debt)(extensions) )
|
||||
FC_REFLECT( graphene::chain::fill_order_operation, (fee)(order_id)(account_id)(pays)(receives) )
|
||||
FC_REFLECT( graphene::chain::fill_order_operation, (fee)(order_id)(account_id)(pays)(receives)(fill_price)(is_maker) )
|
||||
FC_REFLECT( graphene::chain::bid_collateral_operation, (fee)(bidder)(additional_collateral)(debt_covered)(extensions) )
|
||||
FC_REFLECT( graphene::chain::execute_bid_operation, (fee)(bidder)(debt)(collateral) )
|
||||
|
||||
|
||||
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::limit_order_create_operation::fee_parameters_type )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::limit_order_cancel_operation::fee_parameters_type )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::call_order_update_operation::fee_parameters_type )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::bid_collateral_operation::fee_parameters_type )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::limit_order_create_operation )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::limit_order_cancel_operation )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::call_order_update_operation )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::fill_order_operation )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::bid_collateral_operation )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::execute_bid_operation )
|
||||
|
|
|
|||
|
|
@ -190,7 +190,9 @@ namespace graphene { namespace chain {
|
|||
nft_lottery_token_purchase_operation,
|
||||
nft_lottery_reward_operation,
|
||||
nft_lottery_end_operation,
|
||||
random_number_store_operation
|
||||
random_number_store_operation,
|
||||
bid_collateral_operation,
|
||||
execute_bid_operation
|
||||
> operation;
|
||||
|
||||
/// @} // operations group
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@
|
|||
#include <fc/uint128.hpp>
|
||||
#include <fc/static_variant.hpp>
|
||||
|
||||
#include <boost/rational.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
|
|
@ -108,6 +110,7 @@ namespace graphene { namespace chain {
|
|||
|
||||
typedef fc::ecc::private_key private_key_type;
|
||||
typedef fc::sha256 chain_id_type;
|
||||
using ratio_type = boost::rational<int32_t>;
|
||||
|
||||
enum asset_issuer_permission_flags
|
||||
{
|
||||
|
|
@ -216,7 +219,8 @@ namespace graphene { namespace chain {
|
|||
impl_offer_history_object_type,
|
||||
impl_son_statistics_object_type,
|
||||
impl_son_schedule_object_type,
|
||||
impl_nft_lottery_balance_object_type
|
||||
impl_nft_lottery_balance_object_type,
|
||||
impl_collateral_bid_object_type
|
||||
};
|
||||
|
||||
//typedef fc::unsigned_int object_id_type;
|
||||
|
|
@ -328,6 +332,7 @@ namespace graphene { namespace chain {
|
|||
class nft_lottery_balance_object;
|
||||
class son_statistics_object;
|
||||
class son_schedule_object;
|
||||
class collateral_bid_object;
|
||||
|
||||
typedef object_id< implementation_ids, impl_global_property_object_type, global_property_object> global_property_id_type;
|
||||
typedef object_id< implementation_ids, impl_dynamic_global_property_object_type, dynamic_global_property_object> dynamic_global_property_id_type;
|
||||
|
|
@ -360,6 +365,7 @@ namespace graphene { namespace chain {
|
|||
typedef object_id< implementation_ids, impl_nft_lottery_balance_object_type, nft_lottery_balance_object> nft_lottery_balance_id_type;
|
||||
typedef object_id< implementation_ids, impl_son_statistics_object_type, son_statistics_object > son_statistics_id_type;
|
||||
typedef object_id< implementation_ids, impl_son_schedule_object_type, son_schedule_object> son_schedule_id_type;
|
||||
typedef object_id< implementation_ids, impl_collateral_bid_object_type, collateral_bid_object > collateral_bid_id_type;
|
||||
|
||||
typedef fc::array<char, GRAPHENE_MAX_ASSET_SYMBOL_LENGTH> symbol_type;
|
||||
typedef fc::ripemd160 block_id_type;
|
||||
|
|
@ -534,6 +540,7 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type,
|
|||
(impl_son_statistics_object_type)
|
||||
(impl_son_schedule_object_type)
|
||||
(impl_nft_lottery_balance_object_type)
|
||||
(impl_collateral_bid_object_type)
|
||||
)
|
||||
|
||||
FC_REFLECT_TYPENAME( graphene::chain::share_type )
|
||||
|
|
@ -592,6 +599,7 @@ FC_REFLECT_TYPENAME( graphene::chain::son_wallet_withdraw_id_type )
|
|||
FC_REFLECT_TYPENAME( graphene::chain::sidechain_address_id_type )
|
||||
FC_REFLECT_TYPENAME( graphene::chain::sidechain_transaction_id_type )
|
||||
FC_REFLECT_TYPENAME( graphene::chain::random_number_id_type )
|
||||
FC_REFLECT_TYPENAME( graphene::chain::collateral_bid_id_type )
|
||||
|
||||
FC_REFLECT( graphene::chain::void_t, )
|
||||
|
||||
|
|
|
|||
|
|
@ -66,12 +66,32 @@ void_result limit_order_create_evaluator::do_evaluate(const limit_order_create_o
|
|||
return void_result();
|
||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||
|
||||
void limit_order_create_evaluator::convert_fee()
|
||||
{
|
||||
if( db().head_block_time() <= HARDFORK_CORE_604_TIME )
|
||||
generic_evaluator::convert_fee();
|
||||
else
|
||||
if( !trx_state->skip_fee )
|
||||
{
|
||||
if( fee_asset->get_id() != asset_id_type() )
|
||||
{
|
||||
db().modify(*fee_asset_dyn_data, [this](asset_dynamic_data_object& d) {
|
||||
d.fee_pool -= core_fee_paid;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void limit_order_create_evaluator::pay_fee()
|
||||
{
|
||||
if( db().head_block_time() <= HARDFORK_445_TIME )
|
||||
generic_evaluator::pay_fee();
|
||||
else
|
||||
{
|
||||
_deferred_fee = core_fee_paid;
|
||||
if( db().head_block_time() > HARDFORK_CORE_604_TIME && fee_asset->get_id() != asset_id_type() )
|
||||
_deferred_paid_fee = fee_from_account;
|
||||
}
|
||||
}
|
||||
|
||||
object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_operation& op)
|
||||
|
|
@ -92,9 +112,14 @@ object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_o
|
|||
obj.sell_price = op.get_price();
|
||||
obj.expiration = op.expiration;
|
||||
obj.deferred_fee = _deferred_fee;
|
||||
obj.deferred_paid_fee = _deferred_paid_fee;
|
||||
});
|
||||
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);
|
||||
bool filled;
|
||||
if( db().get_dynamic_global_properties().next_maintenance_time <= HARDFORK_CORE_625_TIME )
|
||||
filled = db().apply_order_before_hardfork_625( new_order_object );
|
||||
else
|
||||
filled = db().apply_order( new_order_object );
|
||||
|
||||
FC_ASSERT( !op.fill_or_kill || filled );
|
||||
|
||||
|
|
@ -119,7 +144,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();
|
||||
|
||||
d.cancel_order(*_order, false /* don't create a virtual op*/);
|
||||
d.cancel_limit_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?
|
||||
|
|
@ -133,11 +158,26 @@ 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( 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." );
|
||||
|
||||
_paying_account = &o.funding_account(d);
|
||||
_debt_asset = &o.delta_debt.asset_id(d);
|
||||
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
|
||||
|
|
@ -178,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);
|
||||
});
|
||||
|
|
@ -197,37 +237,50 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat
|
|||
}
|
||||
}
|
||||
|
||||
const auto next_maint_time = d.get_dynamic_global_properties().next_maintenance_time;
|
||||
bool before_core_hardfork_1270 = ( next_maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue
|
||||
|
||||
auto& call_idx = d.get_index_type<call_order_index>().indices().get<by_account>();
|
||||
auto itr = call_idx.find( boost::make_tuple(o.funding_account, o.delta_debt.asset_id) );
|
||||
const call_order_object* call_obj = nullptr;
|
||||
|
||||
optional<price> old_collateralization;
|
||||
|
||||
optional<uint16_t> new_target_cr = o.extensions.value.target_collateral_ratio;
|
||||
|
||||
if( itr == call_idx.end() )
|
||||
{
|
||||
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 ){
|
||||
call_obj = &d.create<call_order_object>( [&o,this,before_core_hardfork_1270]( call_order_object& call ){
|
||||
call.borrower = o.funding_account;
|
||||
call.collateral = o.delta_collateral.amount;
|
||||
call.debt = o.delta_debt.amount;
|
||||
call.call_price = price::call_price(o.delta_debt, o.delta_collateral,
|
||||
_bitasset_data->current_feed.maintenance_collateral_ratio);
|
||||
if( before_core_hardfork_1270 ) // before core-1270 hard fork, calculate call_price here and cache it
|
||||
call.call_price = price::call_price( o.delta_debt, o.delta_collateral,
|
||||
_bitasset_data->current_feed.maintenance_collateral_ratio );
|
||||
else // after core-1270 hard fork, set call_price to 1
|
||||
call.call_price = price( asset( 1, o.delta_collateral.asset_id ), asset( 1, o.delta_debt.asset_id ) );
|
||||
call.target_collateral_ratio = o.extensions.value.target_collateral_ratio;
|
||||
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
call_obj = &*itr;
|
||||
old_collateralization = call_obj->collateralization();
|
||||
|
||||
d.modify( *call_obj, [&]( call_order_object& call ){
|
||||
call.collateral += o.delta_collateral.amount;
|
||||
call.collateral += o.delta_collateral.amount;
|
||||
call.debt += o.delta_debt.amount;
|
||||
if( call.debt > 0 )
|
||||
{
|
||||
call.call_price = price::call_price(call.get_debt(), call.get_collateral(),
|
||||
_bitasset_data->current_feed.maintenance_collateral_ratio);
|
||||
_bitasset_data->current_feed.maintenance_collateral_ratio);
|
||||
}
|
||||
call.target_collateral_ratio = new_target_cr;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -248,10 +301,16 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat
|
|||
|
||||
// check to see if the order needs to be margin called now, but don't allow black swans and require there to be
|
||||
// limit orders available that could be used to fill the order.
|
||||
// Note: due to https://github.com/bitshares/bitshares-core/issues/649,
|
||||
// the first call order may be unable to be updated if the second one is undercollateralized.
|
||||
if( d.check_call_orders( *_debt_asset, false ) )
|
||||
{
|
||||
const auto call_obj = d.find(call_order_id);
|
||||
// if we filled at least one call order, we are OK if we totally filled.
|
||||
// before hard fork core-583: if we filled at least one call order, we are OK if we totally filled.
|
||||
// after hard fork core-583: we want to allow increasing collateral
|
||||
// Note: increasing collateral won't get the call order itself matched (instantly margin called)
|
||||
// if there is at least a call order get matched but didn't cause a black swan event,
|
||||
// current order must have got matched. in this case, it's OK if it's totally filled.
|
||||
GRAPHENE_ASSERT(
|
||||
!call_obj,
|
||||
call_order_update_unfilled_margin_call,
|
||||
|
|
@ -263,20 +322,106 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat
|
|||
{
|
||||
const auto call_obj = d.find(call_order_id);
|
||||
FC_ASSERT( call_obj, "no margin call was executed and yet the call object was deleted" );
|
||||
//edump( (~call_obj->call_price) ("<")( _bitasset_data->current_feed.settlement_price) );
|
||||
// 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",
|
||||
("a", ~call_obj->call_price )("b", _bitasset_data->current_feed.settlement_price)
|
||||
);
|
||||
if( d.head_block_time() <= HARDFORK_CORE_583_TIME ) // TODO remove after hard fork core-583
|
||||
{
|
||||
// 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",
|
||||
("a", ~call_obj->call_price )("b", _bitasset_data->current_feed.settlement_price)
|
||||
);
|
||||
}
|
||||
else // after hard fork, always allow call order to be updated if collateral ratio is increased
|
||||
{
|
||||
// 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,
|
||||
// if collateral ratio is not increased, we throw.
|
||||
// be here, we know no margin call was executed,
|
||||
// so call_obj's collateral ratio should be set only by op
|
||||
FC_ASSERT( ( old_collateralization.valid() && call_obj->collateralization() > *old_collateralization )
|
||||
|| ~call_obj->call_price < _bitasset_data->current_feed.settlement_price,
|
||||
"Can only update to higher collateral ratio if it would trigger a margin call that cannot be fully filled",
|
||||
("new_call_price", ~call_obj->call_price )
|
||||
("settlement_price", _bitasset_data->current_feed.settlement_price)
|
||||
("old_collateralization", old_collateralization)
|
||||
("new_collateralization", call_obj->collateralization() )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return void_result();
|
||||
} FC_CAPTURE_AND_RETHROW( (o) ) }
|
||||
|
||||
void_result bid_collateral_evaluator::do_evaluate(const bid_collateral_operation& o)
|
||||
{ try {
|
||||
database& d = db();
|
||||
|
||||
FC_ASSERT( d.head_block_time() >= HARDFORK_CORE_216_TIME, "Not yet!" );
|
||||
|
||||
_paying_account = &o.bidder(d);
|
||||
_debt_asset = &o.debt_covered.asset_id(d);
|
||||
FC_ASSERT( _debt_asset->is_market_issued(), "Unable to cover ${sym} as it is not a collateralized asset.",
|
||||
("sym", _debt_asset->symbol) );
|
||||
|
||||
_bitasset_data = &_debt_asset->bitasset_data(d);
|
||||
|
||||
FC_ASSERT( _bitasset_data->has_settlement() );
|
||||
|
||||
FC_ASSERT( o.additional_collateral.asset_id == _bitasset_data->options.short_backing_asset );
|
||||
|
||||
FC_ASSERT( !_bitasset_data->is_prediction_market, "Cannot bid on a prediction market!" );
|
||||
|
||||
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 ) );
|
||||
if( bid != index.end() )
|
||||
_bid = &(*bid);
|
||||
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) ) }
|
||||
|
||||
|
||||
void_result bid_collateral_evaluator::do_apply(const bid_collateral_operation& o)
|
||||
{ try {
|
||||
database& d = db();
|
||||
|
||||
if( _bid )
|
||||
d.cancel_bid( *_bid, false );
|
||||
|
||||
if( o.debt_covered.amount == 0 ) return void_result();
|
||||
|
||||
d.adjust_balance( o.bidder, -o.additional_collateral );
|
||||
|
||||
_bid = &d.create<collateral_bid_object>([&]( collateral_bid_object& bid ) {
|
||||
bid.bidder = o.bidder;
|
||||
bid.inv_swan_price = o.additional_collateral / o.debt_covered;
|
||||
});
|
||||
// Note: CORE asset in collateral_bid_object is not counted in account_stats.total_core_in_orders
|
||||
|
||||
return void_result();
|
||||
} FC_CAPTURE_AND_RETHROW( (o) ) }
|
||||
|
||||
} } // graphene::chain
|
||||
|
|
|
|||
299
libraries/chain/market_object.cpp
Normal file
299
libraries/chain/market_object.cpp
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Abit More, and contributors.
|
||||
*
|
||||
* The MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#include <graphene/chain/market_object.hpp>
|
||||
|
||||
#include <boost/multiprecision/cpp_int.hpp>
|
||||
|
||||
#include <functional>
|
||||
|
||||
using namespace graphene::chain;
|
||||
|
||||
/*
|
||||
target_CR = max( target_CR, MCR )
|
||||
target_CR = new_collateral / ( new_debt / feed_price )
|
||||
= ( collateral - max_amount_to_sell ) * feed_price
|
||||
/ ( debt - amount_to_get )
|
||||
= ( collateral - max_amount_to_sell ) * feed_price
|
||||
/ ( debt - round_down(max_amount_to_sell * match_price ) )
|
||||
= ( collateral - max_amount_to_sell ) * feed_price
|
||||
/ ( debt - (max_amount_to_sell * match_price - x) )
|
||||
Note: x is the fraction, 0 <= x < 1
|
||||
=>
|
||||
max_amount_to_sell = ( (debt + x) * target_CR - collateral * feed_price )
|
||||
/ (target_CR * match_price - feed_price)
|
||||
= ( (debt + x) * tCR / DENOM - collateral * fp_debt_amt / fp_coll_amt )
|
||||
/ ( (tCR / DENOM) * (mp_debt_amt / mp_coll_amt) - fp_debt_amt / fp_coll_amt )
|
||||
= ( (debt + x) * tCR * fp_coll_amt * mp_coll_amt - collateral * fp_debt_amt * DENOM * mp_coll_amt)
|
||||
/ ( tCR * mp_debt_amt * fp_coll_amt - fp_debt_amt * DENOM * mp_coll_amt )
|
||||
max_debt_to_cover = max_amount_to_sell * match_price
|
||||
= max_amount_to_sell * mp_debt_amt / mp_coll_amt
|
||||
= ( (debt + x) * tCR * fp_coll_amt * mp_debt_amt - collateral * fp_debt_amt * DENOM * mp_debt_amt)
|
||||
/ (tCR * mp_debt_amt * fp_coll_amt - fp_debt_amt * DENOM * mp_coll_amt)
|
||||
*/
|
||||
share_type call_order_object::get_max_debt_to_cover( price match_price,
|
||||
price feed_price,
|
||||
const uint16_t maintenance_collateral_ratio,
|
||||
const optional<price>& maintenance_collateralization )const
|
||||
{ try {
|
||||
// be defensive here, make sure feed_price is in collateral / debt format
|
||||
if( feed_price.base.asset_id != call_price.base.asset_id )
|
||||
feed_price = ~feed_price;
|
||||
|
||||
FC_ASSERT( feed_price.base.asset_id == call_price.base.asset_id
|
||||
&& feed_price.quote.asset_id == call_price.quote.asset_id );
|
||||
|
||||
bool after_core_hardfork_1270 = maintenance_collateralization.valid();
|
||||
|
||||
// be defensive here, make sure maintenance_collateralization is in collateral / debt format
|
||||
if( after_core_hardfork_1270 )
|
||||
{
|
||||
FC_ASSERT( maintenance_collateralization->base.asset_id == call_price.base.asset_id
|
||||
&& maintenance_collateralization->quote.asset_id == call_price.quote.asset_id );
|
||||
}
|
||||
|
||||
// According to the feed protection rule (https://github.com/cryptonomex/graphene/issues/436),
|
||||
// a call order should only be called when its collateral ratio is not higher than required maintenance collateral ratio.
|
||||
// Although this should be guaranteed by the caller of this function, we still check here to be defensive.
|
||||
// Theoretically this check can be skipped for better performance.
|
||||
//
|
||||
// Before core-1270 hard fork, we check with call_price; afterwards, we check with collateralization().
|
||||
if( ( !after_core_hardfork_1270 && call_price > feed_price )
|
||||
|| ( after_core_hardfork_1270 && collateralization() > *maintenance_collateralization ) )
|
||||
return 0;
|
||||
|
||||
if( !target_collateral_ratio.valid() ) // target cr is not set
|
||||
return debt;
|
||||
|
||||
uint16_t tcr = std::max( *target_collateral_ratio, maintenance_collateral_ratio ); // use mcr if target cr is too small
|
||||
|
||||
price target_collateralization = ( after_core_hardfork_1270 ?
|
||||
feed_price * ratio_type( tcr, GRAPHENE_COLLATERAL_RATIO_DENOM ) :
|
||||
price() );
|
||||
|
||||
// be defensive here, make sure match_price is in collateral / debt format
|
||||
if( match_price.base.asset_id != call_price.base.asset_id )
|
||||
match_price = ~match_price;
|
||||
|
||||
FC_ASSERT( match_price.base.asset_id == call_price.base.asset_id
|
||||
&& match_price.quote.asset_id == call_price.quote.asset_id );
|
||||
|
||||
typedef boost::multiprecision::int256_t i256;
|
||||
i256 mp_debt_amt = match_price.quote.amount.value;
|
||||
i256 mp_coll_amt = match_price.base.amount.value;
|
||||
i256 fp_debt_amt = feed_price.quote.amount.value;
|
||||
i256 fp_coll_amt = feed_price.base.amount.value;
|
||||
|
||||
// firstly we calculate without the fraction (x), the result could be a bit too small
|
||||
i256 numerator = fp_coll_amt * mp_debt_amt * debt.value * tcr
|
||||
- fp_debt_amt * mp_debt_amt * collateral.value * GRAPHENE_COLLATERAL_RATIO_DENOM;
|
||||
if( numerator < 0 ) // feed protected, actually should not be true here, just check to be safe
|
||||
return 0;
|
||||
|
||||
i256 denominator = fp_coll_amt * mp_debt_amt * tcr - fp_debt_amt * mp_coll_amt * GRAPHENE_COLLATERAL_RATIO_DENOM;
|
||||
if( denominator <= 0 ) // black swan
|
||||
return debt;
|
||||
|
||||
// note: if add 1 here, will result in 1.5x imperfection rate;
|
||||
// however, due to rounding, the result could still be a bit too big, thus imperfect.
|
||||
i256 to_cover_i256 = ( numerator / denominator );
|
||||
if( to_cover_i256 >= debt.value ) // avoid possible overflow
|
||||
return debt;
|
||||
share_type to_cover_amt = static_cast< int64_t >( to_cover_i256 );
|
||||
|
||||
// stabilize
|
||||
// note: rounding up-down results in 3x imperfection rate in comparison to down-down-up
|
||||
asset to_pay = asset( to_cover_amt, debt_type() ) * match_price;
|
||||
asset to_cover = to_pay * match_price;
|
||||
to_pay = to_cover.multiply_and_round_up( match_price );
|
||||
|
||||
if( to_cover.amount >= debt || to_pay.amount >= collateral ) // to be safe
|
||||
return debt;
|
||||
FC_ASSERT( to_pay.amount < collateral && to_cover.amount < debt );
|
||||
|
||||
// Check whether the collateral ratio after filled is high enough
|
||||
// Before core-1270 hard fork, we check with call_price; afterwards, we check with collateralization().
|
||||
std::function<bool()> result_is_good = after_core_hardfork_1270 ?
|
||||
std::function<bool()>( [this,&to_cover,&to_pay,target_collateralization]() -> bool
|
||||
{
|
||||
price new_collateralization = ( get_collateral() - to_pay ) / ( get_debt() - to_cover );
|
||||
return ( new_collateralization > target_collateralization );
|
||||
}) :
|
||||
std::function<bool()>( [this,&to_cover,&to_pay,tcr,feed_price]() -> bool
|
||||
{
|
||||
price new_call_price = price::call_price( get_debt() - to_cover, get_collateral() - to_pay, tcr );
|
||||
return ( new_call_price > feed_price );
|
||||
});
|
||||
|
||||
// if the result is good, we return.
|
||||
if( result_is_good() )
|
||||
return to_cover.amount;
|
||||
|
||||
// be here, to_cover is too small due to rounding. deal with the fraction
|
||||
numerator += fp_coll_amt * mp_debt_amt * tcr; // plus the fraction
|
||||
to_cover_i256 = ( numerator / denominator ) + 1;
|
||||
if( to_cover_i256 >= debt.value ) // avoid possible overflow
|
||||
to_cover_i256 = debt.value;
|
||||
to_cover_amt = static_cast< int64_t >( to_cover_i256 );
|
||||
|
||||
asset max_to_pay = ( ( to_cover_amt == debt.value ) ? get_collateral()
|
||||
: asset( to_cover_amt, debt_type() ).multiply_and_round_up( match_price ) );
|
||||
if( max_to_pay.amount > collateral )
|
||||
max_to_pay.amount = collateral;
|
||||
|
||||
asset max_to_cover = ( ( max_to_pay.amount == collateral ) ? get_debt() : ( max_to_pay * match_price ) );
|
||||
if( max_to_cover.amount >= debt ) // to be safe
|
||||
{
|
||||
max_to_pay.amount = collateral;
|
||||
max_to_cover.amount = debt;
|
||||
}
|
||||
|
||||
if( max_to_pay <= to_pay || max_to_cover <= to_cover ) // strange data. should skip binary search and go on, but doesn't help much
|
||||
return debt;
|
||||
FC_ASSERT( max_to_pay > to_pay && max_to_cover > to_cover );
|
||||
|
||||
asset min_to_pay = to_pay;
|
||||
asset min_to_cover = to_cover;
|
||||
|
||||
// try with binary search to find a good value
|
||||
// note: actually binary search can not always provide perfect result here,
|
||||
// due to rounding, collateral ratio is not always increasing while to_pay or to_cover is increasing
|
||||
bool max_is_ok = false;
|
||||
while( true )
|
||||
{
|
||||
// get the mean
|
||||
if( match_price.base.amount < match_price.quote.amount ) // step of collateral is smaller
|
||||
{
|
||||
to_pay.amount = ( min_to_pay.amount + max_to_pay.amount + 1 ) / 2; // should not overflow. round up here
|
||||
if( to_pay.amount == max_to_pay.amount )
|
||||
to_cover.amount = max_to_cover.amount;
|
||||
else
|
||||
{
|
||||
to_cover = to_pay * match_price;
|
||||
if( to_cover.amount >= max_to_cover.amount ) // can be true when max_is_ok is false
|
||||
{
|
||||
to_pay.amount = max_to_pay.amount;
|
||||
to_cover.amount = max_to_cover.amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
to_pay = to_cover.multiply_and_round_up( match_price ); // stabilization, no change or become smaller
|
||||
FC_ASSERT( to_pay.amount < max_to_pay.amount );
|
||||
}
|
||||
}
|
||||
}
|
||||
else // step of debt is smaller or equal
|
||||
{
|
||||
to_cover.amount = ( min_to_cover.amount + max_to_cover.amount ) / 2; // should not overflow. round down here
|
||||
if( to_cover.amount == max_to_cover.amount )
|
||||
to_pay.amount = max_to_pay.amount;
|
||||
else
|
||||
{
|
||||
to_pay = to_cover.multiply_and_round_up( match_price );
|
||||
if( to_pay.amount >= max_to_pay.amount ) // can be true when max_is_ok is false
|
||||
{
|
||||
to_pay.amount = max_to_pay.amount;
|
||||
to_cover.amount = max_to_cover.amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
to_cover = to_pay * match_price; // stabilization, to_cover should have increased
|
||||
if( to_cover.amount >= max_to_cover.amount ) // to be safe
|
||||
{
|
||||
to_pay.amount = max_to_pay.amount;
|
||||
to_cover.amount = max_to_cover.amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check again to see if we've moved away from the minimums, if not, use the maximums directly
|
||||
if( to_pay.amount <= min_to_pay.amount || to_cover.amount <= min_to_cover.amount
|
||||
|| to_pay.amount > max_to_pay.amount || to_cover.amount > max_to_cover.amount )
|
||||
{
|
||||
to_pay.amount = max_to_pay.amount;
|
||||
to_cover.amount = max_to_cover.amount;
|
||||
}
|
||||
|
||||
// check the mean
|
||||
if( to_pay.amount == max_to_pay.amount && ( max_is_ok || to_pay.amount == collateral ) )
|
||||
return to_cover.amount;
|
||||
FC_ASSERT( to_pay.amount < collateral && to_cover.amount < debt );
|
||||
|
||||
// Check whether the result is good
|
||||
if( result_is_good() ) // good
|
||||
{
|
||||
if( to_pay.amount == max_to_pay.amount )
|
||||
return to_cover.amount;
|
||||
max_to_pay.amount = to_pay.amount;
|
||||
max_to_cover.amount = to_cover.amount;
|
||||
max_is_ok = true;
|
||||
}
|
||||
else // not good
|
||||
{
|
||||
if( to_pay.amount == max_to_pay.amount )
|
||||
break;
|
||||
min_to_pay.amount = to_pay.amount;
|
||||
min_to_cover.amount = to_cover.amount;
|
||||
}
|
||||
}
|
||||
|
||||
// be here, max_to_cover is too small due to rounding. search forward
|
||||
for( uint64_t d1 = 0, d2 = 1, d3 = 1; ; d1 = d2, d2 = d3, d3 = d1 + d2 ) // 1,1,2,3,5,8,...
|
||||
{
|
||||
if( match_price.base.amount > match_price.quote.amount ) // step of debt is smaller
|
||||
{
|
||||
to_pay.amount += d2;
|
||||
if( to_pay.amount >= collateral )
|
||||
return debt;
|
||||
to_cover = to_pay * match_price;
|
||||
if( to_cover.amount >= debt )
|
||||
return debt;
|
||||
to_pay = to_cover.multiply_and_round_up( match_price ); // stabilization
|
||||
if( to_pay.amount >= collateral )
|
||||
return debt;
|
||||
}
|
||||
else // step of collateral is smaller or equal
|
||||
{
|
||||
to_cover.amount += d2;
|
||||
if( to_cover.amount >= debt )
|
||||
return debt;
|
||||
to_pay = to_cover.multiply_and_round_up( match_price );
|
||||
if( to_pay.amount >= collateral )
|
||||
return debt;
|
||||
to_cover = to_pay * match_price; // stabilization
|
||||
if( to_cover.amount >= debt )
|
||||
return debt;
|
||||
}
|
||||
|
||||
// check
|
||||
FC_ASSERT( to_pay.amount < collateral && to_cover.amount < debt );
|
||||
|
||||
// Check whether the result is good
|
||||
if( result_is_good() ) // good
|
||||
return to_cover.amount;
|
||||
}
|
||||
|
||||
} FC_CAPTURE_AND_RETHROW( (*this)(feed_price)(match_price)(maintenance_collateral_ratio) ) }
|
||||
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( /*not extern*/, graphene::chain::collateral_bid_object )
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -93,12 +93,119 @@ namespace graphene { namespace chain {
|
|||
FC_THROW_EXCEPTION( fc::assert_exception, "invalid asset * price", ("asset",a)("price",b) );
|
||||
}
|
||||
|
||||
asset asset::multiply_and_round_up( const price& b )const
|
||||
{
|
||||
const asset& a = *this;
|
||||
if( a.asset_id == b.base.asset_id )
|
||||
{
|
||||
FC_ASSERT( b.base.amount.value > 0 );
|
||||
uint128_t result = (uint128_t(a.amount.value) * b.quote.amount.value + b.base.amount.value - 1)/b.base.amount.value;
|
||||
FC_ASSERT( result <= GRAPHENE_MAX_SHARE_SUPPLY );
|
||||
return asset( result.convert_to<int64_t>(), b.quote.asset_id );
|
||||
}
|
||||
else if( a.asset_id == b.quote.asset_id )
|
||||
{
|
||||
FC_ASSERT( b.quote.amount.value > 0 );
|
||||
uint128_t result = (uint128_t(a.amount.value) * b.base.amount.value + b.quote.amount.value - 1)/b.quote.amount.value;
|
||||
FC_ASSERT( result <= GRAPHENE_MAX_SHARE_SUPPLY );
|
||||
return asset( result.convert_to<int64_t>(), b.base.asset_id );
|
||||
}
|
||||
FC_THROW_EXCEPTION( fc::assert_exception, "invalid asset::multiply_and_round_up(price)", ("asset",a)("price",b) );
|
||||
}
|
||||
|
||||
price operator / ( const asset& base, const asset& quote )
|
||||
{ try {
|
||||
FC_ASSERT( base.asset_id != quote.asset_id );
|
||||
return price{base,quote};
|
||||
} FC_CAPTURE_AND_RETHROW( (base)(quote) ) }
|
||||
|
||||
price operator * ( const price& p, const ratio_type& r )
|
||||
{ try {
|
||||
p.validate();
|
||||
|
||||
FC_ASSERT( r.numerator() > 0 && r.denominator() > 0 );
|
||||
|
||||
if( r.numerator() == r.denominator() ) return p;
|
||||
|
||||
boost::rational<int128_t> p128( p.base.amount.value, p.quote.amount.value );
|
||||
boost::rational<int128_t> r128( r.numerator(), r.denominator() );
|
||||
auto cp = p128 * r128;
|
||||
auto ocp = cp;
|
||||
|
||||
bool shrinked = false;
|
||||
bool using_max = false;
|
||||
static const int128_t max( GRAPHENE_MAX_SHARE_SUPPLY );
|
||||
while( cp.numerator() > max || cp.denominator() > max )
|
||||
{
|
||||
if( cp.numerator() == 1 )
|
||||
{
|
||||
cp = boost::rational<int128_t>( 1, max );
|
||||
using_max = true;
|
||||
break;
|
||||
}
|
||||
else if( cp.denominator() == 1 )
|
||||
{
|
||||
cp = boost::rational<int128_t>( max, 1 );
|
||||
using_max = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
cp = boost::rational<int128_t>( cp.numerator() >> 1, cp.denominator() >> 1 );
|
||||
shrinked = true;
|
||||
}
|
||||
}
|
||||
if( shrinked ) // maybe not accurate enough due to rounding, do additional checks here
|
||||
{
|
||||
int128_t num = ocp.numerator();
|
||||
int128_t den = ocp.denominator();
|
||||
if( num > den )
|
||||
{
|
||||
num /= den;
|
||||
if( num > max )
|
||||
num = max;
|
||||
den = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
den /= num;
|
||||
if( den > max )
|
||||
den = max;
|
||||
num = 1;
|
||||
}
|
||||
boost::rational<int128_t> ncp( num, den );
|
||||
if( num == max || den == max ) // it's on the edge, we know it's accurate enough
|
||||
cp = ncp;
|
||||
else
|
||||
{
|
||||
// from the accurate ocp, now we have ncp and cp. use the one which is closer to ocp.
|
||||
// TODO improve performance
|
||||
auto diff1 = abs( ncp - ocp );
|
||||
auto diff2 = abs( cp - ocp );
|
||||
if( diff1 < diff2 ) cp = ncp;
|
||||
}
|
||||
}
|
||||
|
||||
price np = asset( static_cast<int64_t>(cp.numerator()), p.base.asset_id )
|
||||
/ asset( static_cast<int64_t>(cp.denominator()), p.quote.asset_id );
|
||||
|
||||
if( shrinked || using_max )
|
||||
{
|
||||
if( ( r.numerator() > r.denominator() && np < p )
|
||||
|| ( r.numerator() < r.denominator() && np > p ) )
|
||||
// even with an accurate result, if p is out of valid range, return it
|
||||
np = p;
|
||||
}
|
||||
|
||||
np.validate();
|
||||
return np;
|
||||
} FC_CAPTURE_AND_RETHROW( (p)(r.numerator())(r.denominator()) ) }
|
||||
|
||||
price operator / ( const price& p, const ratio_type& r )
|
||||
{ try {
|
||||
return p * ratio_type( r.denominator(), r.numerator() );
|
||||
} FC_CAPTURE_AND_RETHROW( (p)(r.numerator())(r.denominator()) ) }
|
||||
|
||||
price price::max( asset_id_type base, asset_id_type quote ) { return asset( share_type(GRAPHENE_MAX_SHARE_SUPPLY), base ) / asset( share_type(1), quote); }
|
||||
price price::min( asset_id_type base, asset_id_type quote ) { return asset( 1, base ) / asset( GRAPHENE_MAX_SHARE_SUPPLY, quote); }
|
||||
|
||||
|
|
@ -120,7 +227,6 @@ namespace graphene { namespace chain {
|
|||
*/
|
||||
price price::call_price( const asset& debt, const asset& collateral, uint16_t collateral_ratio)
|
||||
{ try {
|
||||
//wdump((debt)(collateral)(collateral_ratio));
|
||||
boost::rational<int128_t> swan(debt.amount.value,collateral.amount.value);
|
||||
boost::rational<int128_t> ratio( collateral_ratio, GRAPHENE_COLLATERAL_RATIO_DENOM );
|
||||
auto cp = swan * ratio;
|
||||
|
|
@ -152,9 +258,11 @@ namespace graphene { namespace chain {
|
|||
FC_ASSERT( maximum_short_squeeze_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO );
|
||||
FC_ASSERT( maintenance_collateral_ratio >= GRAPHENE_MIN_COLLATERAL_RATIO );
|
||||
FC_ASSERT( maintenance_collateral_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO );
|
||||
max_short_squeeze_price(); // make sure that it doesn't overflow
|
||||
// Note: there was code here calling `max_short_squeeze_price();` before core-1270 hard fork,
|
||||
// in order to make sure that it doesn't overflow,
|
||||
// but the code doesn't actually check overflow, and it won't overflow, so the code is removed.
|
||||
|
||||
//FC_ASSERT( maintenance_collateral_ratio >= maximum_short_squeeze_ratio );
|
||||
// Note: not checking `maintenance_collateral_ratio >= maximum_short_squeeze_ratio` since launch
|
||||
} FC_CAPTURE_AND_RETHROW( (*this) ) }
|
||||
|
||||
bool price_feed::is_for( asset_id_type asset_id ) const
|
||||
|
|
@ -171,16 +279,34 @@ namespace graphene { namespace chain {
|
|||
FC_CAPTURE_AND_RETHROW( (*this) )
|
||||
}
|
||||
|
||||
price price_feed::max_short_squeeze_price()const
|
||||
// This function is kept here due to potential different behavior in edge cases.
|
||||
// TODO check after core-1270 hard fork to see if we can safely remove it
|
||||
price price_feed::max_short_squeeze_price_before_hf_1270()const
|
||||
{
|
||||
boost::rational<int128_t> sp( settlement_price.base.amount.value, settlement_price.quote.amount.value ); //debt.amount.value,collateral.amount.value);
|
||||
// settlement price is in debt/collateral
|
||||
boost::rational<int128_t> sp( settlement_price.base.amount.value, settlement_price.quote.amount.value );
|
||||
boost::rational<int128_t> ratio( GRAPHENE_COLLATERAL_RATIO_DENOM, maximum_short_squeeze_ratio );
|
||||
auto cp = sp * ratio;
|
||||
|
||||
while( cp.numerator() > GRAPHENE_MAX_SHARE_SUPPLY || cp.denominator() > GRAPHENE_MAX_SHARE_SUPPLY )
|
||||
cp = boost::rational<int128_t>( (cp.numerator() >> 1)+(cp.numerator()&1), (cp.denominator() >> 1)+(cp.denominator()&1) );
|
||||
cp = boost::rational<int128_t>( (cp.numerator() >> 1)+(cp.numerator()&1),
|
||||
(cp.denominator() >> 1)+(cp.denominator()&1) );
|
||||
|
||||
return (asset( cp.numerator().convert_to<int64_t>(), settlement_price.base.asset_id ) / asset( cp.denominator().convert_to<int64_t>(), settlement_price.quote.asset_id ));
|
||||
return ( asset( cp.numerator().convert_to<int64_t>(), settlement_price.base.asset_id )
|
||||
/ asset( cp.denominator().convert_to<int64_t>(), settlement_price.quote.asset_id ) );
|
||||
}
|
||||
|
||||
price price_feed::max_short_squeeze_price()const
|
||||
{
|
||||
// settlement price is in debt/collateral
|
||||
return settlement_price * ratio_type( GRAPHENE_COLLATERAL_RATIO_DENOM, maximum_short_squeeze_ratio );
|
||||
}
|
||||
|
||||
price price_feed::maintenance_collateralization()const
|
||||
{
|
||||
if( settlement_price.is_null() )
|
||||
return price();
|
||||
return ~settlement_price * ratio_type( maintenance_collateral_ratio, GRAPHENE_COLLATERAL_RATIO_DENOM );
|
||||
}
|
||||
|
||||
// compile-time table of powers of 10 using template metaprogramming
|
||||
|
|
|
|||
|
|
@ -46,12 +46,21 @@ void call_order_update_operation::validate()const
|
|||
FC_ASSERT( delta_collateral.amount != 0 || delta_debt.amount != 0 );
|
||||
} FC_CAPTURE_AND_RETHROW((*this)) }
|
||||
|
||||
void bid_collateral_operation::validate()const
|
||||
{ try {
|
||||
FC_ASSERT( fee.amount >= 0 );
|
||||
FC_ASSERT( debt_covered.amount == 0 || (debt_covered.amount > 0 && additional_collateral.amount > 0) );
|
||||
} FC_CAPTURE_AND_RETHROW((*this)) }
|
||||
|
||||
} } // graphene::chain
|
||||
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( /*not extern*/, graphene::chain::limit_order_create_operation::fee_parameters_type )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( /*not extern*/, graphene::chain::limit_order_cancel_operation::fee_parameters_type )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( /*not extern*/, graphene::chain::call_order_update_operation::fee_parameters_type )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( /*not extern*/, graphene::chain::bid_collateral_operation::fee_parameters_type )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( /*not extern*/, graphene::chain::limit_order_create_operation )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( /*not extern*/, graphene::chain::limit_order_cancel_operation )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( /*not extern*/, graphene::chain::call_order_update_operation )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( /*not extern*/, graphene::chain::fill_order_operation )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( /*not extern*/, graphene::chain::bid_collateral_operation )
|
||||
GRAPHENE_EXTERNAL_SERIALIZATION( /*not extern*/, graphene::chain::execute_bid_operation )
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -106,10 +106,9 @@ struct operation_process_fill_order
|
|||
ho.time = time;
|
||||
ho.op = o;
|
||||
});
|
||||
|
||||
/*
|
||||
hkey.sequence += 200;
|
||||
itr = history_idx.lower_bound( hkey );
|
||||
/*
|
||||
while( itr != history_idx.end() )
|
||||
{
|
||||
if( itr->key.base == hkey.base && itr->key.quote == hkey.quote )
|
||||
|
|
@ -122,90 +121,105 @@ struct operation_process_fill_order
|
|||
*/
|
||||
|
||||
|
||||
/* Note: below is not true, because global settlement creates only one fill_order_op.
|
||||
* for every matched order there are two fill order operations created, one for
|
||||
* each side. We can filter the duplicates by only considering the fill operations where
|
||||
* the base > quote
|
||||
*/
|
||||
/*
|
||||
if( o.pays.asset_id > o.receives.asset_id )
|
||||
{
|
||||
//ilog( " skipping because base > quote" );
|
||||
return;
|
||||
}
|
||||
*/
|
||||
if( !o.is_maker )
|
||||
return;
|
||||
|
||||
bucket_key key;
|
||||
key.base = o.pays.asset_id;
|
||||
key.quote = o.receives.asset_id;
|
||||
|
||||
price trade_price = o.pays / o.receives;
|
||||
|
||||
|
||||
if( key.base > key.quote )
|
||||
{
|
||||
std::swap( key.base, key.quote );
|
||||
trade_price = ~trade_price;
|
||||
}
|
||||
|
||||
price fill_price = o.fill_price;
|
||||
if( fill_price.base.asset_id > fill_price.quote.asset_id )
|
||||
fill_price = ~fill_price;
|
||||
|
||||
auto max_history = _plugin.max_history();
|
||||
for( auto bucket : buckets )
|
||||
{
|
||||
auto cutoff = (fc::time_point() + fc::seconds( bucket * max_history));
|
||||
auto cutoff = (fc::time_point() + fc::seconds( bucket * max_history));
|
||||
|
||||
bucket_key key;
|
||||
key.base = o.pays.asset_id;
|
||||
key.quote = o.receives.asset_id;
|
||||
key.seconds = bucket;
|
||||
key.open = fc::time_point() + fc::seconds((_now.sec_since_epoch() / key.seconds) * key.seconds);
|
||||
|
||||
|
||||
/** for every matched order there are two fill order operations created, one for
|
||||
* each side. We can filter the duplicates by only considering the fill operations where
|
||||
* the base > quote
|
||||
*/
|
||||
if( key.base > key.quote )
|
||||
{
|
||||
//ilog( " skipping because base > quote" );
|
||||
continue;
|
||||
}
|
||||
|
||||
price trade_price = o.pays / o.receives;
|
||||
|
||||
key.seconds = bucket;
|
||||
key.open = fc::time_point() + fc::seconds((_now.sec_since_epoch() / key.seconds) * key.seconds);
|
||||
|
||||
const auto& by_key_idx = bucket_idx.indices().get<by_key>();
|
||||
auto itr = by_key_idx.find( key );
|
||||
if( itr == by_key_idx.end() )
|
||||
{ // create new bucket
|
||||
const auto& by_key_idx = bucket_idx.indices().get<by_key>();
|
||||
auto itr = by_key_idx.find( key );
|
||||
if( itr == by_key_idx.end() )
|
||||
{ // create new bucket
|
||||
/* const auto& obj = */
|
||||
db.create<bucket_object>( [&]( bucket_object& b ){
|
||||
b.key = key;
|
||||
b.quote_volume += trade_price.quote.amount;
|
||||
b.base_volume += trade_price.base.amount;
|
||||
b.open_base = trade_price.base.amount;
|
||||
b.open_quote = trade_price.quote.amount;
|
||||
b.close_base = trade_price.base.amount;
|
||||
b.close_quote = trade_price.quote.amount;
|
||||
b.high_base = b.close_base;
|
||||
b.high_quote = b.close_quote;
|
||||
b.low_base = b.close_base;
|
||||
b.low_quote = b.close_quote;
|
||||
});
|
||||
//wlog( " creating bucket ${b}", ("b",obj) );
|
||||
}
|
||||
else
|
||||
{ // update existing bucket
|
||||
//wlog( " before updating bucket ${b}", ("b",*itr) );
|
||||
db.modify( *itr, [&]( bucket_object& b ){
|
||||
b.base_volume += trade_price.base.amount;
|
||||
b.key = key;
|
||||
b.quote_volume += trade_price.quote.amount;
|
||||
b.close_base = trade_price.base.amount;
|
||||
b.close_quote = trade_price.quote.amount;
|
||||
if( b.high() < trade_price )
|
||||
{
|
||||
b.high_base = b.close_base;
|
||||
b.high_quote = b.close_quote;
|
||||
}
|
||||
if( b.low() > trade_price )
|
||||
{
|
||||
b.low_base = b.close_base;
|
||||
b.low_quote = b.close_quote;
|
||||
}
|
||||
});
|
||||
//wlog( " after bucket bucket ${b}", ("b",*itr) );
|
||||
}
|
||||
b.base_volume += trade_price.base.amount;
|
||||
b.open_base = fill_price.base.amount;
|
||||
b.open_quote = fill_price.quote.amount;
|
||||
b.close_base = fill_price.base.amount;
|
||||
b.close_quote = fill_price.quote.amount;
|
||||
b.high_base = b.close_base;
|
||||
b.high_quote = b.close_quote;
|
||||
b.low_base = b.close_base;
|
||||
b.low_quote = b.close_quote;
|
||||
});
|
||||
//wlog( " creating bucket ${b}", ("b",obj) );
|
||||
}
|
||||
else
|
||||
{ // update existing bucket
|
||||
//wlog( " before updating bucket ${b}", ("b",*itr) );
|
||||
db.modify( *itr, [&]( bucket_object& b ){
|
||||
b.base_volume += trade_price.base.amount;
|
||||
b.quote_volume += trade_price.quote.amount;
|
||||
b.close_base = fill_price.base.amount;
|
||||
b.close_quote = fill_price.quote.amount;
|
||||
if( b.high() < fill_price )
|
||||
{
|
||||
b.high_base = b.close_base;
|
||||
b.high_quote = b.close_quote;
|
||||
}
|
||||
if( b.low() > fill_price )
|
||||
{
|
||||
b.low_base = b.close_base;
|
||||
b.low_quote = b.close_quote;
|
||||
}
|
||||
});
|
||||
//wlog( " after bucket bucket ${b}", ("b",*itr) );
|
||||
}
|
||||
|
||||
if( max_history != 0 )
|
||||
{
|
||||
key.open = fc::time_point_sec();
|
||||
auto itr = by_key_idx.lower_bound( key );
|
||||
if( max_history != 0 )
|
||||
{
|
||||
key.open = fc::time_point_sec();
|
||||
auto itr = by_key_idx.lower_bound( key );
|
||||
|
||||
while( itr != by_key_idx.end() &&
|
||||
itr->key.base == key.base &&
|
||||
itr->key.quote == key.quote &&
|
||||
itr->key.seconds == bucket &&
|
||||
itr->key.open < cutoff )
|
||||
{
|
||||
// elog( " removing old bucket ${b}", ("b", *itr) );
|
||||
auto old_itr = itr;
|
||||
++itr;
|
||||
db.remove( *old_itr );
|
||||
}
|
||||
}
|
||||
while( itr != by_key_idx.end() &&
|
||||
itr->key.base == key.base &&
|
||||
itr->key.quote == key.quote &&
|
||||
itr->key.seconds == bucket &&
|
||||
itr->key.open < cutoff )
|
||||
{
|
||||
// elog( " removing old bucket ${b}", ("b", *itr) );
|
||||
auto old_itr = itr;
|
||||
++itr;
|
||||
db.remove( *old_itr );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -223,7 +237,12 @@ void market_history_plugin_impl::update_market_histories( const signed_block& b
|
|||
for( const optional< operation_history_object >& o_op : hist )
|
||||
{
|
||||
if( o_op.valid() )
|
||||
o_op->op.visit( operation_process_fill_order( _self, b.timestamp ) );
|
||||
{
|
||||
try
|
||||
{
|
||||
o_op->op.visit( operation_process_fill_order( _self, b.timestamp ) );
|
||||
} FC_CAPTURE_AND_LOG( (o_op) )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -254,6 +254,7 @@ void database_fixture::verify_asset_supplies( const database& db )
|
|||
const auto& settle_index = db.get_index_type<force_settlement_index>().indices();
|
||||
const auto& tournaments_index = db.get_index_type<tournament_index>().indices();
|
||||
const auto& asst_index = db.get_index_type<asset_index>().indices();
|
||||
const auto& bids = db.get_index_type<collateral_bid_index>().indices();
|
||||
|
||||
map<asset_id_type,share_type> total_balances;
|
||||
map<asset_id_type,share_type> total_debts;
|
||||
|
|
@ -273,6 +274,8 @@ void database_fixture::verify_asset_supplies( const database& db )
|
|||
total_balances[b.asset_type] += b.balance;
|
||||
for( const force_settlement_object& s : settle_index )
|
||||
total_balances[s.balance.asset_id] += s.balance.amount;
|
||||
for( const collateral_bid_object& b : bids )
|
||||
total_balances[b.inv_swan_price.base.asset_id] += b.inv_swan_price.base.amount;
|
||||
for( const account_statistics_object& a : statistics_index )
|
||||
{
|
||||
reported_core_in_orders += a.total_core_in_orders;
|
||||
|
|
@ -607,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 )
|
||||
|
|
@ -857,21 +860,26 @@ digest_type database_fixture::digest( const transaction& tx )
|
|||
return tx.digest();
|
||||
}
|
||||
|
||||
const limit_order_object*database_fixture::create_sell_order(account_id_type user, const asset& amount, const asset& recv)
|
||||
const limit_order_object*database_fixture::create_sell_order(account_id_type user, const asset& amount, const asset& recv,
|
||||
const time_point_sec order_expiration,
|
||||
const price& fee_core_exchange_rate )
|
||||
{
|
||||
auto r = create_sell_order(user(db), amount, recv);
|
||||
auto r = create_sell_order(user(db), amount, recv, order_expiration, fee_core_exchange_rate);
|
||||
verify_asset_supplies(db);
|
||||
return r;
|
||||
}
|
||||
|
||||
const limit_order_object* database_fixture::create_sell_order( const account_object& user, const asset& amount, const asset& recv )
|
||||
const limit_order_object* database_fixture::create_sell_order( const account_object& user, const asset& amount, const asset& recv,
|
||||
const time_point_sec order_expiration,
|
||||
const price& fee_core_exchange_rate )
|
||||
{
|
||||
limit_order_create_operation buy_order;
|
||||
buy_order.seller = user.id;
|
||||
buy_order.amount_to_sell = amount;
|
||||
buy_order.min_to_receive = recv;
|
||||
buy_order.expiration = order_expiration;
|
||||
trx.operations.push_back(buy_order);
|
||||
for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op);
|
||||
for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op, fee_core_exchange_rate);
|
||||
trx.validate();
|
||||
auto processed = db.push_transaction(trx, ~0);
|
||||
trx.operations.clear();
|
||||
|
|
@ -966,6 +974,38 @@ void database_fixture::publish_feed( const asset_object& mia, const account_obje
|
|||
verify_asset_supplies(db);
|
||||
}
|
||||
|
||||
/***
|
||||
* @brief helper method to add a price feed
|
||||
*
|
||||
* Adds a price feed for asset2, pushes the transaction, and generates the block
|
||||
*
|
||||
* @param fixture the database_fixture
|
||||
* @param publisher who is publishing the feed
|
||||
* @param asset1 the base asset
|
||||
* @param amount1 the amount of the base asset
|
||||
* @param asset2 the quote asset
|
||||
* @param amount2 the amount of the quote asset
|
||||
* @param core_id id of core (helps with core_exchange_rate)
|
||||
*/
|
||||
void database_fixture::publish_feed(const account_id_type& publisher,
|
||||
const asset_id_type& asset1, int64_t amount1,
|
||||
const asset_id_type& asset2, int64_t amount2,
|
||||
const asset_id_type& core_id)
|
||||
{
|
||||
const asset_object& a1 = asset1(db);
|
||||
const asset_object& a2 = asset2(db);
|
||||
const asset_object& core = core_id(db);
|
||||
asset_publish_feed_operation op;
|
||||
op.publisher = publisher;
|
||||
op.asset_id = asset2;
|
||||
op.feed.settlement_price = ~price(a1.amount(amount1),a2.amount(amount2));
|
||||
op.feed.core_exchange_rate = ~price(core.amount(amount1), a2.amount(amount2));
|
||||
trx.operations.push_back(std::move(op));
|
||||
PUSH_TX( db, trx, ~0);
|
||||
generate_block();
|
||||
trx.clear();
|
||||
}
|
||||
|
||||
void database_fixture::force_global_settle( const asset_object& what, const price& p )
|
||||
{ try {
|
||||
set_expiration( db, trx );
|
||||
|
|
@ -1039,6 +1079,22 @@ void database_fixture::cover(const account_object& who, asset what, asset collat
|
|||
verify_asset_supplies(db);
|
||||
} FC_CAPTURE_AND_RETHROW( (who.name)(what)(collateral) ) }
|
||||
|
||||
void database_fixture::bid_collateral(const account_object& who, const asset& to_bid, const asset& to_cover)
|
||||
{ try {
|
||||
set_expiration( db, trx );
|
||||
trx.operations.clear();
|
||||
bid_collateral_operation bid;
|
||||
bid.bidder = who.id;
|
||||
bid.additional_collateral = to_bid;
|
||||
bid.debt_covered = to_cover;
|
||||
trx.operations.push_back(bid);
|
||||
for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op);
|
||||
trx.validate();
|
||||
db.push_transaction(trx, ~0);
|
||||
trx.operations.clear();
|
||||
verify_asset_supplies(db);
|
||||
} FC_CAPTURE_AND_RETHROW( (who.name)(to_bid)(to_cover) ) }
|
||||
|
||||
void database_fixture::fund_fee_pool( const account_object& from, const asset_object& asset_to_fund, const share_type amount )
|
||||
{
|
||||
asset_fund_fee_pool_operation fund;
|
||||
|
|
|
|||
|
|
@ -228,6 +228,22 @@ struct database_fixture {
|
|||
void update_feed_producers(const asset_object& mia, flat_set<account_id_type> producers);
|
||||
void publish_feed(asset_id_type mia, account_id_type by, const price_feed& f)
|
||||
{ publish_feed(mia(db), by(db), f); }
|
||||
/***
|
||||
* @brief helper method to add a price feed
|
||||
*
|
||||
* Adds a price feed for asset2, pushes the transaction, and generates the block
|
||||
*
|
||||
* @param publisher who is publishing the feed
|
||||
* @param asset1 the base asset
|
||||
* @param amount1 the amount of the base asset
|
||||
* @param asset2 the quote asset
|
||||
* @param amount2 the amount of the quote asset
|
||||
* @param core_id id of core (helps with core_exchange_rate)
|
||||
*/
|
||||
void publish_feed(const account_id_type& publisher,
|
||||
const asset_id_type& asset1, int64_t amount1,
|
||||
const asset_id_type& asset2, int64_t amount2,
|
||||
const asset_id_type& core_id);
|
||||
void publish_feed(const asset_object& mia, const account_object& by, const price_feed& f);
|
||||
const call_order_object* borrow(account_id_type who, asset what, asset collateral)
|
||||
{ return borrow(who(db), what, collateral); }
|
||||
|
|
@ -235,6 +251,7 @@ struct database_fixture {
|
|||
void cover(account_id_type who, asset what, asset collateral_freed)
|
||||
{ cover(who(db), what, collateral_freed); }
|
||||
void cover(const account_object& who, asset what, asset collateral_freed);
|
||||
void bid_collateral(const account_object& who, const asset& to_bid, const asset& to_cover);
|
||||
|
||||
const asset_object& get_asset( const string& symbol )const;
|
||||
const account_object& get_account( const string& name )const;
|
||||
|
|
@ -282,8 +299,12 @@ struct database_fixture {
|
|||
uint64_t fund( const account_object& account, const asset& amount = asset(500000) );
|
||||
digest_type digest( const transaction& tx );
|
||||
void sign( signed_transaction& trx, const fc::ecc::private_key& key );
|
||||
const limit_order_object* create_sell_order( account_id_type user, const asset& amount, const asset& recv );
|
||||
const limit_order_object* create_sell_order( const account_object& user, const asset& amount, const asset& recv );
|
||||
const limit_order_object* create_sell_order( account_id_type user, const asset& amount, const asset& recv,
|
||||
const time_point_sec order_expiration = time_point_sec::maximum(),
|
||||
const price& fee_core_exchange_rate = price::unit_price());
|
||||
const limit_order_object* create_sell_order( const account_object& user, const asset& amount, const asset& recv,
|
||||
const time_point_sec order_expiration = time_point_sec::maximum(),
|
||||
const price& fee_core_exchange_rate = price::unit_price());
|
||||
asset cancel_limit_order( const limit_order_object& order );
|
||||
void transfer( account_id_type from, account_id_type to, const asset& amount, const asset& fee = asset() );
|
||||
void transfer( const account_object& from, const account_object& to, const asset& amount, const asset& fee = asset() );
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
206
tests/tests/market_tests.cpp
Normal file
206
tests/tests/market_tests.cpp
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <graphene/chain/hardfork.hpp>
|
||||
|
||||
#include <graphene/chain/protocol/market.hpp>
|
||||
#include <graphene/chain/market_object.hpp>
|
||||
|
||||
#include "../common/database_fixture.hpp"
|
||||
|
||||
using namespace graphene::chain;
|
||||
using namespace graphene::chain::test;
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(market_tests, database_fixture)
|
||||
|
||||
/***
|
||||
* Reproduce bitshares-core issue #338 #343 #453 #606 #625 #649
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE(issue_338_etc)
|
||||
{ try {
|
||||
generate_blocks(HARDFORK_615_TIME); // get around Graphene issue #615 feed expiration bug
|
||||
generate_block();
|
||||
|
||||
set_expiration( db, trx );
|
||||
|
||||
ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(feedproducer));
|
||||
|
||||
const auto& bitusd = create_bitasset("USDBIT", feedproducer_id);
|
||||
const auto& core = asset_id_type()(db);
|
||||
asset_id_type usd_id = bitusd.id;
|
||||
asset_id_type core_id = core.id;
|
||||
|
||||
int64_t init_balance(1000000);
|
||||
|
||||
transfer(committee_account, buyer_id, asset(init_balance));
|
||||
transfer(committee_account, borrower_id, asset(init_balance));
|
||||
transfer(committee_account, borrower2_id, asset(init_balance));
|
||||
transfer(committee_account, borrower3_id, asset(init_balance));
|
||||
update_feed_producers( bitusd, {feedproducer.id} );
|
||||
|
||||
price_feed current_feed;
|
||||
current_feed.maintenance_collateral_ratio = 1750;
|
||||
current_feed.maximum_short_squeeze_ratio = 1100;
|
||||
current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5);
|
||||
publish_feed( bitusd, feedproducer, current_feed );
|
||||
// start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7
|
||||
const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000));
|
||||
call_order_id_type call_id = call.id;
|
||||
// create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7
|
||||
const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500));
|
||||
call_order_id_type call2_id = call2.id;
|
||||
// create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7
|
||||
const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000));
|
||||
call_order_id_type call3_id = call3.id;
|
||||
transfer(borrower, seller, bitusd.amount(1000));
|
||||
|
||||
BOOST_CHECK_EQUAL( 1000, call.debt.value );
|
||||
BOOST_CHECK_EQUAL( 15000, call.collateral.value );
|
||||
BOOST_CHECK_EQUAL( 1000, get_balance(seller, bitusd) );
|
||||
BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );
|
||||
|
||||
// adjust price feed to get call_order into margin call territory
|
||||
current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10);
|
||||
publish_feed( bitusd, feedproducer, current_feed );
|
||||
// settlement price = 1/10, mssp = 1/11
|
||||
|
||||
// This order slightly below the call price will not be matched #606
|
||||
limit_order_id_type sell_low = create_sell_order(seller, bitusd.amount(7), core.amount(59))->id;
|
||||
// This order above the MSSP will not be matched
|
||||
limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->id;
|
||||
// This would match but is blocked by sell_low?! #606
|
||||
limit_order_id_type sell_med = create_sell_order(seller, bitusd.amount(7), core.amount(60))->id;
|
||||
|
||||
cancel_limit_order( sell_med(db) );
|
||||
cancel_limit_order( sell_high(db) );
|
||||
cancel_limit_order( sell_low(db) );
|
||||
|
||||
// current implementation: an incoming limit order will be filled at the
|
||||
// requested price #338
|
||||
BOOST_CHECK( !create_sell_order(seller, bitusd.amount(7), core.amount(60)) );
|
||||
BOOST_CHECK_EQUAL( 993, get_balance(seller, bitusd) );
|
||||
BOOST_CHECK_EQUAL( 60, get_balance(seller, core) );
|
||||
BOOST_CHECK_EQUAL( 993, call.debt.value );
|
||||
BOOST_CHECK_EQUAL( 14940, call.collateral.value );
|
||||
|
||||
limit_order_id_type buy_low = create_sell_order(buyer, asset(90), bitusd.amount(10))->id;
|
||||
// margin call takes precedence
|
||||
BOOST_CHECK( !create_sell_order(seller, bitusd.amount(7), core.amount(60)) );
|
||||
BOOST_CHECK_EQUAL( 986, get_balance(seller, bitusd) );
|
||||
BOOST_CHECK_EQUAL( 120, get_balance(seller, core) );
|
||||
BOOST_CHECK_EQUAL( 986, call.debt.value );
|
||||
BOOST_CHECK_EQUAL( 14880, call.collateral.value );
|
||||
|
||||
limit_order_id_type buy_med = create_sell_order(buyer, asset(105), bitusd.amount(10))->id;
|
||||
// margin call takes precedence
|
||||
BOOST_CHECK( !create_sell_order(seller, bitusd.amount(7), core.amount(70)) );
|
||||
BOOST_CHECK_EQUAL( 979, get_balance(seller, bitusd) );
|
||||
BOOST_CHECK_EQUAL( 190, get_balance(seller, core) );
|
||||
BOOST_CHECK_EQUAL( 979, call.debt.value );
|
||||
BOOST_CHECK_EQUAL( 14810, call.collateral.value );
|
||||
|
||||
limit_order_id_type buy_high = create_sell_order(buyer, asset(115), bitusd.amount(10))->id;
|
||||
// margin call still has precedence (!) #625
|
||||
BOOST_CHECK( !create_sell_order(seller, bitusd.amount(7), core.amount(77)) );
|
||||
BOOST_CHECK_EQUAL( 972, get_balance(seller, bitusd) );
|
||||
BOOST_CHECK_EQUAL( 267, get_balance(seller, core) );
|
||||
BOOST_CHECK_EQUAL( 972, call.debt.value );
|
||||
BOOST_CHECK_EQUAL( 14733, call.collateral.value );
|
||||
|
||||
cancel_limit_order( buy_high(db) );
|
||||
cancel_limit_order( buy_med(db) );
|
||||
cancel_limit_order( buy_low(db) );
|
||||
|
||||
// call with more usd
|
||||
BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700), core.amount(7700)) );
|
||||
BOOST_CHECK_EQUAL( 272, get_balance(seller, bitusd) );
|
||||
BOOST_CHECK_EQUAL( 7967, get_balance(seller, core) );
|
||||
BOOST_CHECK_EQUAL( 272, call.debt.value );
|
||||
BOOST_CHECK_EQUAL( 7033, call.collateral.value );
|
||||
|
||||
// at this moment, collateralization of call is 7033 / 272 = 25.8
|
||||
// collateralization of call2 is 15500 / 1000 = 15.5
|
||||
// collateralization of call3 is 16000 / 1000 = 16
|
||||
|
||||
// call more, still matches with the first call order #343
|
||||
BOOST_CHECK( !create_sell_order(seller, bitusd.amount(10), core.amount(110)) );
|
||||
BOOST_CHECK_EQUAL( 262, get_balance(seller, bitusd) );
|
||||
BOOST_CHECK_EQUAL( 8077, get_balance(seller, core) );
|
||||
BOOST_CHECK_EQUAL( 262, call.debt.value );
|
||||
BOOST_CHECK_EQUAL( 6923, call.collateral.value );
|
||||
|
||||
// at this moment, collateralization of call is 6923 / 262 = 26.4
|
||||
// collateralization of call2 is 15500 / 1000 = 15.5
|
||||
// collateralization of call3 is 16000 / 1000 = 16
|
||||
|
||||
// force settle
|
||||
force_settle( seller, bitusd.amount(10) );
|
||||
BOOST_CHECK_EQUAL( 252, get_balance(seller, bitusd) );
|
||||
BOOST_CHECK_EQUAL( 8077, get_balance(seller, core) );
|
||||
BOOST_CHECK_EQUAL( 262, call.debt.value );
|
||||
BOOST_CHECK_EQUAL( 6923, call.collateral.value );
|
||||
|
||||
// generate blocks to let the settle order execute (price feed will expire after it)
|
||||
generate_blocks( HARDFORK_615_TIME + fc::hours(25) );
|
||||
// call2 get settled #343
|
||||
BOOST_CHECK_EQUAL( 252, get_balance(seller_id, usd_id) );
|
||||
BOOST_CHECK_EQUAL( 8177, get_balance(seller_id, core_id) );
|
||||
BOOST_CHECK_EQUAL( 262, call_id(db).debt.value );
|
||||
BOOST_CHECK_EQUAL( 6923, call_id(db).collateral.value );
|
||||
BOOST_CHECK_EQUAL( 990, call2_id(db).debt.value );
|
||||
BOOST_CHECK_EQUAL( 15400, call2_id(db).collateral.value );
|
||||
|
||||
set_expiration( db, trx );
|
||||
update_feed_producers( usd_id(db), {feedproducer_id} );
|
||||
|
||||
// at this moment, collateralization of call is 8177 / 252 = 32.4
|
||||
// collateralization of call2 is 15400 / 990 = 15.5
|
||||
// collateralization of call3 is 16000 / 1000 = 16
|
||||
|
||||
// adjust price feed to get call2 into black swan territory, but not the first call order
|
||||
current_feed.settlement_price = asset(1, usd_id) / asset(20, core_id);
|
||||
publish_feed( usd_id(db), feedproducer_id(db), current_feed );
|
||||
// settlement price = 1/20, mssp = 1/22
|
||||
|
||||
// black swan event doesn't occur #649
|
||||
BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() );
|
||||
|
||||
// generate a block
|
||||
generate_block();
|
||||
|
||||
set_expiration( db, trx );
|
||||
update_feed_producers( usd_id(db), {feedproducer_id} );
|
||||
|
||||
// adjust price feed back
|
||||
current_feed.settlement_price = asset(1, usd_id) / asset(10, core_id);
|
||||
publish_feed( usd_id(db), feedproducer_id(db), current_feed );
|
||||
// settlement price = 1/10, mssp = 1/11
|
||||
|
||||
transfer(borrower2_id, seller_id, asset(1000, usd_id));
|
||||
transfer(borrower3_id, seller_id, asset(1000, usd_id));
|
||||
|
||||
// Re-create sell_low, slightly below the call price, will not be matched, will expire soon
|
||||
sell_low = create_sell_order(seller_id(db), asset(7, usd_id), asset(59), db.head_block_time()+fc::seconds(300) )->id;
|
||||
// This would match but is blocked by sell_low, it has an amount same as call's debt which will be full filled later
|
||||
sell_med = create_sell_order(seller_id(db), asset(262, usd_id), asset(2620))->id; // 1/10
|
||||
// Another big order above sell_med, blocked
|
||||
limit_order_id_type sell_med2 = create_sell_order(seller_id(db), asset(1200, usd_id), asset(12120))->id; // 1/10.1
|
||||
// Another small order above sell_med2, blocked
|
||||
limit_order_id_type sell_med3 = create_sell_order(seller_id(db), asset(120, usd_id), asset(1224))->id; // 1/10.2
|
||||
|
||||
// generate a block, sell_low will expire
|
||||
BOOST_TEST_MESSAGE( "Expire sell_low" );
|
||||
generate_blocks( HARDFORK_615_TIME + fc::hours(26) );
|
||||
BOOST_CHECK( db.find<limit_order_object>( sell_low ) == nullptr );
|
||||
|
||||
// #453 multiple order matching issue occurs
|
||||
BOOST_CHECK( db.find<limit_order_object>( sell_med ) == nullptr ); // sell_med get filled
|
||||
BOOST_CHECK( db.find<limit_order_object>( sell_med2 ) != nullptr ); // sell_med2 is still there
|
||||
BOOST_CHECK( db.find<limit_order_object>( sell_med3 ) == nullptr ); // sell_med3 get filled
|
||||
BOOST_CHECK( db.find<call_order_object>( call_id ) == nullptr ); // the first call order get filled
|
||||
BOOST_CHECK( db.find<call_order_object>( call2_id ) == nullptr ); // the second call order get filled
|
||||
BOOST_CHECK( db.find<call_order_object>( call3_id ) != nullptr ); // the third call order is still there
|
||||
|
||||
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue