diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 2eafd284de..e9c1f58bd3 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -1290,14 +1291,13 @@ vector database_api_impl::get_call_orders(const std::string& { FC_ASSERT( limit <= 300 ); - const asset_id_type asset_a_id = get_asset_from_string(a)->id; - const auto& call_index = _db.get_index_type().indices().get(); - const asset_object& mia = _db.get(asset_a_id); - price index_price = price::min(mia.bitasset_data(_db).options.short_backing_asset, mia.get_id()); + const asset_object* mia = get_asset_from_string(a); + const auto& call_index = _db.get_index_type().indices().get(); + price index_price = price::min( mia->bitasset_data(_db).options.short_backing_asset, mia->get_id() ); vector< call_order_object> result; - auto itr_min = call_index.lower_bound(index_price.min()); - auto itr_max = call_index.lower_bound(index_price.max()); + auto itr_min = call_index.lower_bound(index_price); + auto itr_max = call_index.upper_bound(index_price.max()); while( itr_min != itr_max && result.size() < limit ) { result.emplace_back(*itr_min); @@ -2057,10 +2057,12 @@ set database_api::get_required_signatures( const signed_transac set database_api_impl::get_required_signatures( const signed_transaction& trx, const flat_set& available_keys )const { + bool allow_non_immediate_owner = ( _db.head_block_time() >= HARDFORK_CORE_584_TIME ); auto result = trx.get_required_signatures( _db.get_chain_id(), available_keys, [&]( account_id_type id ){ return &id(_db).active; }, [&]( account_id_type id ){ return &id(_db).owner; }, + allow_non_immediate_owner, _db.get_global_properties().parameters.max_authority_depth ); return result; } @@ -2076,6 +2078,7 @@ set
database_api::get_potential_address_signatures( const signed_transa set database_api_impl::get_potential_signatures( const signed_transaction& trx )const { + bool allow_non_immediate_owner = ( _db.head_block_time() >= HARDFORK_CORE_584_TIME ); set result; trx.get_required_signatures( _db.get_chain_id(), @@ -2094,6 +2097,7 @@ set database_api_impl::get_potential_signatures( const signed_t result.insert(k); return &auth; }, + allow_non_immediate_owner, _db.get_global_properties().parameters.max_authority_depth ); @@ -2141,10 +2145,12 @@ bool database_api::verify_authority( const signed_transaction& trx )const bool database_api_impl::verify_authority( const signed_transaction& trx )const { + bool allow_non_immediate_owner = ( _db.head_block_time() >= HARDFORK_CORE_584_TIME ); trx.verify_authority( _db.get_chain_id(), [this]( account_id_type id ){ return &id(_db).active; }, [this]( account_id_type id ){ return &id(_db).owner; }, - _db.get_global_properties().parameters.max_authority_depth ); + allow_non_immediate_owner, + _db.get_global_properties().parameters.max_authority_depth ); return true; } @@ -2166,7 +2172,8 @@ bool database_api_impl::verify_account_authority( const string& account_name_or_ { graphene::chain::verify_authority(ops, keys, [this]( account_id_type id ){ return &id(_db).active; }, - [this]( account_id_type id ){ return &id(_db).owner; } ); + [this]( account_id_type id ){ return &id(_db).owner; }, + true ); } catch (fc::exception& ex) { diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 6fe3f47883..71d2fd2d2e 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -58,7 +58,7 @@ add_library( graphene_chain protocol/fee_schedule.cpp protocol/confidential.cpp protocol/vote.cpp - + protocol/htlc.cpp genesis_state.cpp get_config.cpp @@ -77,6 +77,7 @@ add_library( graphene_chain vesting_balance_evaluator.cpp withdraw_permission_evaluator.cpp worker_evaluator.cpp + htlc_evaluator.cpp confidential_evaluator.cpp special_authority.cpp buyback.cpp diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 9194a02d25..d809983283 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -35,6 +35,20 @@ #include namespace graphene { namespace chain { +namespace detail { + // TODO review and remove code below and links to it after hf_1268 + void check_asset_options_hf_1268(const fc::time_point_sec& block_time, const asset_options& options) + { + if( block_time < HARDFORK_1268_TIME ) + { + FC_ASSERT( !options.extensions.value.reward_percent.valid(), + "Asset extension reward percent is only available after HARDFORK_1268_TIME!"); + + FC_ASSERT( !options.extensions.value.whitelist_market_fee_sharing.valid(), + "Asset extension whitelist_market_fee_sharing is only available after HARDFORK_1268_TIME!"); + } + } +} void_result asset_create_evaluator::do_evaluate( const asset_create_operation& op ) { try { @@ -45,6 +59,8 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o FC_ASSERT( op.common_options.whitelist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities ); FC_ASSERT( op.common_options.blacklist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities ); + detail::check_asset_options_hf_1268(d.head_block_time(), op.common_options); + // Check that all authorities do exist for( auto id : op.common_options.whitelist_authorities ) d.get_object(id); @@ -277,6 +293,8 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) validate_new_issuer( d, a, *o.new_issuer ); } + detail::check_asset_options_hf_1268(d.head_block_time(), o.new_options); + if( (d.head_block_time() < HARDFORK_572_TIME) || (a.dynamic_asset_data_id(d).current_supply != 0) ) { // new issuer_permissions must be subset of old issuer permissions @@ -638,7 +656,7 @@ 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; + 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 @@ -689,7 +707,7 @@ static bool update_bitasset_object_options( if( should_update_feeds ) { const auto old_feed = bdo.current_feed; - bdo.update_median_feeds( db.head_block_time() ); + 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 @@ -766,8 +784,9 @@ void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_f { 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_bitasset_data_object& bitasset_to_update = asset_to_update->bitasset_data(d); - d.modify( bitasset_to_update, [&o,head_time](asset_bitasset_data_object& a) { + d.modify( bitasset_to_update, [&o,head_time,next_maint_time](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 //publishers who are being kept. @@ -791,7 +810,7 @@ void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_f { a.feeds[acc]; } - a.update_median_feeds( head_time ); + a.update_median_feeds( head_time, next_maint_time ); }); // Process margin calls, allow black swan, not for a new limit order d.check_call_orders( *asset_to_update, true, false, &bitasset_to_update ); @@ -969,27 +988,48 @@ 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 = *asset_ptr; const asset_bitasset_data_object& bad = *bitasset_ptr; 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) ) { - if( bad.has_settlement() ) // implies head_block_time > HARDFORK_CORE_216_TIME + // 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( !bad.current_feed.settlement_price.is_null() - && ( mia_dyn.current_supply == 0 - || ~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 ) ) + 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); } // Process margin calls, allow black swan, not for a new limit order diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index 47fd3c146b..c6b6ca0d82 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -23,6 +23,7 @@ */ #include #include +#include #include @@ -43,16 +44,10 @@ share_type asset_bitasset_data_object::max_force_settlement_volume(share_type cu return volume.to_uint64(); } -/****** - * @brief calculate the median feed - * - * This calculates the median feed. It sets the current_feed_publication_time - * and current_feed member variables - * - * @param current_time the time to use in the calculations - */ -void graphene::chain::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> current_feeds; // find feeds that were alive at current_time @@ -73,13 +68,18 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds(time_point 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; } @@ -100,6 +100,9 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds(time_point 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(); } @@ -157,7 +160,7 @@ asset asset_object::amount_from_string(string amount_string) const satoshis *= -1; return amount(satoshis); - } FC_CAPTURE_AND_RETHROW( (amount_string) ) } +} FC_CAPTURE_AND_RETHROW( (amount_string) ) } string asset_object::amount_to_string(share_type amount) const { diff --git a/libraries/chain/committee_member_evaluator.cpp b/libraries/chain/committee_member_evaluator.cpp index b01fa95faa..e81737e161 100644 --- a/libraries/chain/committee_member_evaluator.cpp +++ b/libraries/chain/committee_member_evaluator.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -75,6 +76,9 @@ void_result committee_member_update_global_parameters_evaluator::do_evaluate(con { try { FC_ASSERT(trx_state->_is_proposed_trx); + FC_ASSERT( db().head_block_time() > HARDFORK_CORE_1468_TIME || !o.new_parameters.extensions.value.updatable_htlc_options.valid(), + "Unable to set HTLC parameters until hardfork." ); + return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } diff --git a/libraries/chain/db_balance.cpp b/libraries/chain/db_balance.cpp index caa1eff63b..2a48679e3e 100644 --- a/libraries/chain/db_balance.cpp +++ b/libraries/chain/db_balance.cpp @@ -28,6 +28,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -81,9 +82,81 @@ void database::adjust_balance(account_id_type account, asset delta ) } FC_CAPTURE_AND_RETHROW( (account)(delta) ) } +namespace detail { + + /** + * Used as a key to search vesting_balance_object in the index + */ + struct vbo_mfs_key + { + account_id_type account_id; + asset_id_type asset_id; + + vbo_mfs_key(const account_id_type& account, const asset_id_type& asset): + account_id(account), + asset_id(asset) + {} + + bool operator()(const vbo_mfs_key& k, const vesting_balance_object& vbo)const + { + return ( vbo.balance_type == vesting_balance_type::market_fee_sharing ) && + ( k.asset_id == vbo.balance.asset_id ) && + ( k.account_id == vbo.owner ); + } + + uint64_t operator()(const vbo_mfs_key& k)const + { + return vbo_mfs_hash(k.account_id, k.asset_id); + } + }; +} //detail + +asset database::get_market_fee_vesting_balance(const account_id_type &account_id, const asset_id_type &asset_id) +{ + auto& vesting_balances = get_index_type().indices().get(); + const auto& key = detail::vbo_mfs_key{account_id, asset_id}; + auto vbo_it = vesting_balances.find(key, key, key); + + if( vbo_it == vesting_balances.end() ) + { + return asset(0, asset_id); + } + return vbo_it->balance; +} + +void database::deposit_market_fee_vesting_balance(const account_id_type &account_id, const asset &delta) +{ try { + FC_ASSERT( delta.amount >= 0, "Invalid negative value for balance"); + + if( delta.amount == 0 ) + return; + + auto& vesting_balances = get_index_type().indices().get(); + const auto& key = detail::vbo_mfs_key{account_id, delta.asset_id}; + auto vbo_it = vesting_balances.find(key, key, key); + + auto block_time = head_block_time(); + + if( vbo_it == vesting_balances.end() ) + { + create([&account_id, &delta, &block_time](vesting_balance_object &vbo) { + vbo.owner = account_id; + vbo.balance = delta; + vbo.balance_type = vesting_balance_type::market_fee_sharing; + vbo.policy = instant_vesting_policy{}; + }); + } else { + modify( *vbo_it, [&block_time, &delta]( vesting_balance_object& vbo ) + { + vbo.deposit_vested(block_time, delta); + }); + } +} FC_CAPTURE_AND_RETHROW( (account_id)(delta) ) } + optional< vesting_balance_id_type > database::deposit_lazy_vesting( const optional< vesting_balance_id_type >& ovbid, share_type amount, uint32_t req_vesting_seconds, + vesting_balance_type balance_type, account_id_type req_owner, bool require_vesting ) { @@ -117,6 +190,7 @@ optional< vesting_balance_id_type > database::deposit_lazy_vesting( { _vbo.owner = req_owner; _vbo.balance = amount; + _vbo.balance_type = balance_type; cdd_vesting_policy policy; policy.vesting_seconds = req_vesting_seconds; @@ -152,6 +226,7 @@ void database::deposit_cashback(const account_object& acct, share_type amount, b acct.cashback_vb, amount, get_global_properties().parameters.cashback_vesting_period_seconds, + vesting_balance_type::cashback, acct.id, require_vesting ); @@ -179,6 +254,7 @@ void database::deposit_witness_pay(const witness_object& wit, share_type amount) wit.pay_vb, amount, get_global_properties().parameters.witness_pay_vesting_seconds, + vesting_balance_type::witness, wit.witness_account, true ); diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index e340d2dea1..f3ebec3a1c 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -582,6 +582,7 @@ void database::_apply_block( const signed_block& next_block ) clear_expired_transactions(); clear_expired_proposals(); clear_expired_orders(); + clear_expired_htlcs(); update_expired_feeds(); // this will update expired feeds and some core exchange rates update_core_exchange_rates(); // this will update remaining core exchange rates update_withdraw_permissions(); @@ -631,9 +632,14 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx if( !(skip & skip_transaction_signatures) ) { + bool allow_non_immediate_owner = ( head_block_time() >= HARDFORK_CORE_584_TIME ); auto get_active = [&]( account_id_type id ) { return &id(*this).active; }; auto get_owner = [&]( account_id_type id ) { return &id(*this).owner; }; - trx.verify_authority( chain_id, get_active, get_owner, get_global_properties().parameters.max_authority_depth ); + trx.verify_authority( chain_id, + get_active, + get_owner, + allow_non_immediate_owner, + get_global_properties().parameters.max_authority_depth ); } //Skip all manner of expiration and TaPoS checking if we're on block 1; It's impossible that the transaction is @@ -653,6 +659,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. @@ -745,6 +754,7 @@ void database::_precompute_parallel( const Trx* trx, const size_t count, const u for( size_t i = 0; i < count; ++i, ++trx ) { trx->validate(); // TODO - parallelize wrt confidential operations + trx->get_packed_size(); if( !(skip&skip_transaction_dupe_check) ) trx->id(); if( !(skip&skip_transaction_signatures) ) diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 884a7b3c91..36e19f2ac5 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include @@ -61,6 +62,7 @@ #include #include #include +#include #include @@ -125,6 +127,9 @@ const uint8_t witness_object::type_id; const uint8_t worker_object::space_id; const uint8_t worker_object::type_id; +const uint8_t htlc_object::space_id; +const uint8_t htlc_object::type_id; + void database::initialize_evaluators() { @@ -173,6 +178,9 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); } void database::initialize_indexes() @@ -201,6 +209,7 @@ void database::initialize_indexes() add_index< primary_index >(); add_index< primary_index >(); add_index< primary_index >(); + add_index< primary_index< htlc_index> >(); //Implementation object indexes add_index< primary_index >(); @@ -220,7 +229,6 @@ void database::initialize_indexes() add_index< primary_index< special_authority_index > >(); add_index< primary_index< buyback_index > >(); add_index< primary_index >(); - add_index< primary_index< simple_index< fba_accumulator_object > > >(); } diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 2fdfa0f401..d7f5c3f5b8 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -846,7 +846,9 @@ void database::process_bids( const asset_bitasset_data_object& bad ) _cancel_bids_and_revive_mpa( to_revive, bad ); } -void update_and_match_call_orders( database& db ) +/// 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()) ); @@ -867,7 +869,30 @@ void update_and_match_call_orders( database& db ) 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().indices().get() ) + { + 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().indices().get(); auto itr = asset_idx.lower_bound( true /** market issued */ ); while( itr != asset_idx.end() ) @@ -877,7 +902,7 @@ void update_and_match_call_orders( database& db ) // 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 updating all call orders for hardfork core-343 at block ${n}", ("n",db.head_block_num()) ); + wlog( "Done matching call orders at block ${n}", ("n",db.head_block_num()) ); } void database::process_bitassets() @@ -918,6 +943,49 @@ void database::process_bitassets() } } +/**** + * @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().indices().get(); + 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( current_asset, [current_supply](asset_object& obj) { + obj.options.max_supply = graphene::chain::share_type(std::min(current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY)); + }); + } + } +} + +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().indices() ) + { + db.modify( d, update_bitasset ); + } +} + /****** * @brief one-time data process for hard fork core-868-890 * @@ -939,6 +1007,7 @@ void database::process_bitassets() // * NOTE: 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) ); @@ -998,8 +1067,8 @@ void process_hf_868_890( database& db, bool skip_check_call_orders ) } // always update the median feed due to https://github.com/bitshares/bitshares-core/issues/890 - db.modify( bitasset_data, [&head_time]( asset_bitasset_data_object &obj ) { - obj.update_median_feeds( head_time ); + 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 ); @@ -1210,28 +1279,48 @@ 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 - bool to_update_and_match_call_orders = false; + // 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 = true; + 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 ); + 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 ) + && !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; }); - // We need to do it after updated next_maintenance_time, to apply new rules here - if( to_update_and_match_call_orders ) - update_and_match_call_orders(*this); + // 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(); diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 6b8f67ea1c..3d42f9abc9 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -28,10 +28,21 @@ #include #include #include +#include #include -namespace graphene { namespace chain { +namespace graphene { namespace chain { namespace detail { + + uint64_t calculate_percent(const share_type& value, uint16_t percent) + { + fc::uint128 a(value.value); + a *= percent; + a /= GRAPHENE_100_PERCENT; + return a.to_uint64(); + } + +} //detail /** * All margin positions are force closed at the swan price @@ -51,8 +62,7 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett const asset_dynamic_data_object& mia_dyn = mia.dynamic_asset_data_id(*this); auto original_mia_supply = mia_dyn.current_supply; - const call_order_index& call_index = get_index_type(); - const auto& call_price_index = call_index.indices().get(); + const auto& call_price_index = get_index_type().indices().get(); auto maint_time = get_dynamic_global_properties().next_maintenance_time; bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding @@ -83,9 +93,8 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett FC_ASSERT( fill_call_order( order, pays, order.get_debt(), settlement_price, true ) ); // call order is maker } - modify( bitasset, [&]( asset_bitasset_data_object& obj ){ - assert( collateral_gathered.asset_id == settlement_price.quote.asset_id ); - obj.settlement_price = mia.amount(original_mia_supply) / collateral_gathered; //settlement_price; + modify( bitasset, [&mia,original_mia_supply,&collateral_gathered]( asset_bitasset_data_object& obj ){ + obj.settlement_price = mia.amount(original_mia_supply) / collateral_gathered; obj.settlement_fund = collateral_gathered.amount; }); @@ -93,7 +102,7 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett /// that is a lie, the supply didn't change. We need to capture the current supply before /// filling all call orders and then restore it afterward. Then in the force settlement /// evaluator reduce the supply - modify( mia_dyn, [&]( asset_dynamic_data_object& obj ){ + modify( mia_dyn, [original_mia_supply]( asset_dynamic_data_object& obj ){ obj.current_supply = original_mia_supply; }); @@ -170,9 +179,15 @@ void database::execute_bid( const collateral_bid_object& bid, share_type debt_co call.borrower = bid.bidder; call.collateral = bid.inv_swan_price.base.amount + collateral_from_fund; call.debt = debt_covered; - call.call_price = price::call_price(asset(debt_covered, bid.inv_swan_price.quote.asset_id), - asset(call.collateral, bid.inv_swan_price.base.asset_id), - current_feed.maintenance_collateral_ratio); + // don't calculate call_price after core-1270 hard fork + if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_1270_TIME ) + // bid.inv_swan_price is in collateral / debt + call.call_price = price( asset( 1, bid.inv_swan_price.base.asset_id ), + asset( 1, bid.inv_swan_price.quote.asset_id ) ); + else + call.call_price = price::call_price( asset(debt_covered, bid.inv_swan_price.quote.asset_id), + asset(call.collateral, bid.inv_swan_price.base.asset_id), + current_feed.maintenance_collateral_ratio ); }); // Note: CORE asset in collateral_bid_object is not counted in account_stats.total_core_in_orders @@ -427,6 +442,9 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo // 5. the call order's collateral ratio is below or equals to MCR // 6. the limit order provided a good price + 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 + bool to_check_call_orders = false; const asset_object& sell_asset = sell_asset_id( *this ); const asset_bitasset_data_object* sell_abd = nullptr; @@ -439,7 +457,10 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo && !sell_abd->has_settlement() && !sell_abd->current_feed.settlement_price.is_null() ) { - call_match_price = ~sell_abd->current_feed.max_short_squeeze_price(); + if( before_core_hardfork_1270 ) + call_match_price = ~sell_abd->current_feed.max_short_squeeze_price_before_hf_1270(); + else + call_match_price = ~sell_abd->current_feed.max_short_squeeze_price(); if( ~new_order_object.sell_price <= call_match_price ) // new limit order price is good enough to match a call to_check_call_orders = true; } @@ -457,7 +478,33 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo finished = ( match( new_order_object, *old_limit_itr, old_limit_itr->sell_price ) != 2 ); } - if( !finished ) + if( !finished && !before_core_hardfork_1270 ) // TODO refactor or cleanup duplicate code after core-1270 hard fork + { + // check if there are margin calls + const auto& call_collateral_idx = get_index_type().indices().get(); + auto call_min = price::min( recv_asset_id, sell_asset_id ); + while( !finished ) + { + // hard fork core-343 and core-625 took place at same time, + // always check call order with least collateral ratio + auto call_itr = call_collateral_idx.lower_bound( call_min ); + if( call_itr == call_collateral_idx.end() + || call_itr->debt_type() != sell_asset_id + // feed protected https://github.com/cryptonomex/graphene/issues/436 + || call_itr->collateralization() > sell_abd->current_maintenance_collateralization ) + break; + // hard fork core-338 and core-625 took place at same time, not checking HARDFORK_CORE_338_TIME here. + int match_result = match( new_order_object, *call_itr, call_match_price, + sell_abd->current_feed.settlement_price, + sell_abd->current_feed.maintenance_collateral_ratio, + sell_abd->current_maintenance_collateralization ); + // match returns 1 or 3 when the new order was fully filled. In this case, we stop matching; otherwise keep matching. + // since match can return 0 due to BSIP38 (hard fork core-834), we no longer only check if the result is 2. + if( match_result == 1 || match_result == 3 ) + finished = true; + } + } + else if( !finished ) // and before core-1270 hard fork { // check if there are margin calls const auto& call_price_idx = get_index_type().indices().get(); @@ -474,7 +521,8 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo // assume hard fork core-338 and core-625 will take place at same time, not checking HARDFORK_CORE_338_TIME here. int match_result = match( new_order_object, *call_itr, call_match_price, sell_abd->current_feed.settlement_price, - sell_abd->current_feed.maintenance_collateral_ratio ); + sell_abd->current_feed.maintenance_collateral_ratio, + optional() ); // match returns 1 or 3 when the new order was fully filled. In this case, we stop matching; otherwise keep matching. // since match can return 0 due to BSIP38 (hard fork core-834), we no longer only check if the result is 2. if( match_result == 1 || match_result == 3 ) @@ -580,7 +628,8 @@ int database::match( const limit_order_object& usd, const limit_order_object& co } int database::match( const limit_order_object& bid, const call_order_object& ask, const price& match_price, - const price& feed_price, const uint16_t maintenance_collateral_ratio ) + const price& feed_price, const uint16_t maintenance_collateral_ratio, + const optional& maintenance_collateralization ) { FC_ASSERT( bid.sell_asset_id() == ask.debt_type() ); FC_ASSERT( bid.receive_asset_id() == ask.collateral_type() ); @@ -606,7 +655,10 @@ int database::match( const limit_order_object& bid, const call_order_object& ask // TODO if we're sure `before_core_hardfork_834` is always false, remove the check asset usd_to_buy = ( before_core_hardfork_834 ? ask.get_debt() : - asset( ask.get_max_debt_to_cover( match_price, feed_price, maintenance_collateral_ratio ), + asset( ask.get_max_debt_to_cover( match_price, + feed_price, + maintenance_collateral_ratio, + maintenance_collateralization ), ask.debt_type() ) ); asset call_pays, call_receives, order_pays, order_receives; @@ -775,7 +827,10 @@ bool database::fill_limit_order( const limit_order_object& order, const asset& p const account_object& seller = order.seller(*this); const asset_object& recv_asset = receives.asset_id(*this); - auto issuer_fees = pay_market_fees( recv_asset, receives ); + auto issuer_fees = ( head_block_time() < HARDFORK_1268_TIME ) ? + pay_market_fees(recv_asset, receives) : + pay_market_fees(seller, recv_asset, receives); + pay_order( seller, receives - issuer_fees, pays ); assert( pays.asset_id != receives.asset_id ); @@ -837,9 +892,17 @@ bool database::fill_call_order( const call_order_object& order, const asset& pay collateral_freed = o.get_collateral(); o.collateral = 0; } - else if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_343_TIME ) - o.call_price = price::call_price( o.get_debt(), o.get_collateral(), - mia.bitasset_data(*this).current_feed.maintenance_collateral_ratio ); + else + { + auto maint_time = get_dynamic_global_properties().next_maintenance_time; + // update call_price after core-343 hard fork, + // but don't update call_price after core-1270 hard fork + if( maint_time <= HARDFORK_CORE_1270_TIME && maint_time > HARDFORK_CORE_343_TIME ) + { + o.call_price = price::call_price( o.get_debt(), o.get_collateral(), + mia.bitasset_data(*this).current_feed.maintenance_collateral_ratio ); + } + } }); // update current supply @@ -934,12 +997,14 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa const limit_order_index& limit_index = get_index_type(); const auto& limit_price_index = limit_index.indices().get(); + bool before_core_hardfork_1270 = ( maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue + // looking for limit orders selling the most USD for the least CORE auto max_price = price::max( mia.id, bitasset.options.short_backing_asset ); // stop when limit orders are selling too little USD for too much CORE - auto min_price = bitasset.current_feed.max_short_squeeze_price(); + auto min_price = ( before_core_hardfork_1270 ? bitasset.current_feed.max_short_squeeze_price_before_hf_1270() + : bitasset.current_feed.max_short_squeeze_price() ); - assert( max_price.base.asset_id == min_price.base.asset_id ); // NOTE limit_price_index is sorted from greatest to least auto limit_itr = limit_price_index.lower_bound( max_price ); auto limit_end = limit_price_index.upper_bound( min_price ); @@ -949,11 +1014,26 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa const call_order_index& call_index = get_index_type(); const auto& call_price_index = call_index.indices().get(); + const auto& call_collateral_index = call_index.indices().get(); 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 ); + + auto call_price_itr = call_price_index.begin(); + auto call_price_end = call_price_itr; + auto call_collateral_itr = call_collateral_index.begin(); + auto call_collateral_end = call_collateral_itr; + + if( before_core_hardfork_1270 ) + { + call_price_itr = call_price_index.lower_bound( call_min ); + call_price_end = call_price_index.upper_bound( call_max ); + } + else + { + call_collateral_itr = call_collateral_index.lower_bound( call_min ); + call_collateral_end = call_collateral_index.upper_bound( call_max ); + } bool filled_limit = false; bool margin_called = false; @@ -972,15 +1052,18 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa bool before_core_hardfork_834 = ( maint_time <= HARDFORK_CORE_834_TIME ); // target collateral ratio option while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance by passing in iterators - && call_itr != call_end - && limit_itr != limit_end ) + && limit_itr != limit_end + && ( ( !before_core_hardfork_1270 && call_collateral_itr != call_collateral_end ) + || ( before_core_hardfork_1270 && call_price_itr != call_price_end ) ) ) { bool filled_call = false; - const call_order_object& call_order = *call_itr; + const call_order_object& call_order = ( before_core_hardfork_1270 ? *call_price_itr : *call_collateral_itr ); // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436 - if( after_hardfork_436 && bitasset.current_feed.settlement_price > ~call_order.call_price ) + if( ( !before_core_hardfork_1270 && bitasset.current_maintenance_collateralization < call_order.collateralization() ) + || ( before_core_hardfork_1270 + && after_hardfork_436 && bitasset.current_feed.settlement_price > ~call_order.call_price ) ) return margin_called; const limit_order_object& limit_order = *limit_itr; @@ -1004,10 +1087,19 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa return true; } - if( !before_core_hardfork_834 ) + if( !before_core_hardfork_1270 ) + { + usd_to_buy.amount = call_order.get_max_debt_to_cover( match_price, + bitasset.current_feed.settlement_price, + bitasset.current_feed.maintenance_collateral_ratio, + bitasset.current_maintenance_collateralization ); + } + else if( !before_core_hardfork_834 ) + { usd_to_buy.amount = call_order.get_max_debt_to_cover( match_price, - bitasset.current_feed.settlement_price, - bitasset.current_feed.maintenance_collateral_ratio ); + bitasset.current_feed.settlement_price, + bitasset.current_feed.maintenance_collateral_ratio ); + } asset usd_for_sale = limit_order.amount_for_sale(); asset call_pays, call_receives, order_pays, order_receives; @@ -1071,11 +1163,13 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa order_pays = call_receives; if( filled_call && before_core_hardfork_343 ) - ++call_itr; + ++call_price_itr; // when for_new_limit_order is true, the call order is maker, otherwise the call order is taker fill_call_order( call_order, call_pays, call_receives, match_price, for_new_limit_order ); - if( !before_core_hardfork_343 ) - call_itr = call_price_index.lower_bound( call_min ); + if( !before_core_hardfork_1270 ) + call_collateral_itr = call_collateral_index.lower_bound( call_min ); + else if( !before_core_hardfork_343 ) + call_price_itr = call_price_index.lower_bound( call_min ); auto next_limit_itr = std::next( limit_itr ); // when for_new_limit_order is true, the limit order is taker, otherwise the limit order is maker @@ -1109,10 +1203,8 @@ asset database::calculate_market_fee( const asset_object& trade_asset, const ass if( trade_asset.options.market_fee_percent == 0 ) return trade_asset.amount(0); - fc::uint128 a(trade_amount.amount.value); - a *= trade_asset.options.market_fee_percent; - a /= GRAPHENE_100_PERCENT; - asset percent_fee = trade_asset.amount(a.to_uint64()); + auto value = detail::calculate_percent(trade_amount.amount, trade_asset.options.market_fee_percent); + asset percent_fee = trade_asset.amount(value); if( percent_fee.amount > trade_asset.options.max_market_fee ) percent_fee.amount = trade_asset.options.max_market_fee; @@ -1123,7 +1215,7 @@ asset database::calculate_market_fee( const asset_object& trade_asset, const ass asset database::pay_market_fees( const asset_object& recv_asset, const asset& receives ) { auto issuer_fees = calculate_market_fee( recv_asset, receives ); - assert(issuer_fees <= receives ); + FC_ASSERT( issuer_fees <= receives, "Market fee shouldn't be greater than receives"); //Don't dirty undo state if not actually collecting any fees if( issuer_fees.amount > 0 ) @@ -1138,4 +1230,55 @@ asset database::pay_market_fees( const asset_object& recv_asset, const asset& re return issuer_fees; } +asset database::pay_market_fees(const account_object& seller, const asset_object& recv_asset, const asset& receives ) +{ + const auto issuer_fees = calculate_market_fee( recv_asset, receives ); + FC_ASSERT( issuer_fees <= receives, "Market fee shouldn't be greater than receives"); + //Don't dirty undo state if not actually collecting any fees + if ( issuer_fees.amount > 0 ) + { + // calculate and pay rewards + asset reward = recv_asset.amount(0); + + auto is_rewards_allowed = [&recv_asset, &seller]() { + const auto &white_list = recv_asset.options.extensions.value.whitelist_market_fee_sharing; + return ( !white_list || (*white_list).empty() || ( (*white_list).find(seller.registrar) != (*white_list).end() ) ); + }; + + if ( is_rewards_allowed() ) + { + const auto reward_percent = recv_asset.options.extensions.value.reward_percent; + if ( reward_percent && *reward_percent ) + { + const auto reward_value = detail::calculate_percent(issuer_fees.amount, *reward_percent); + if ( reward_value > 0 && is_authorized_asset(*this, seller.registrar(*this), recv_asset) ) + { + reward = recv_asset.amount(reward_value); + FC_ASSERT( reward < issuer_fees, "Market reward should be less than issuer fees"); + // cut referrer percent from reward + const auto referrer_rewards_percentage = seller.referrer_rewards_percentage; + const auto referrer_rewards_value = detail::calculate_percent(reward.amount, referrer_rewards_percentage); + auto registrar_reward = reward; + + if ( referrer_rewards_value > 0 && is_authorized_asset(*this, seller.referrer(*this), recv_asset)) + { + FC_ASSERT ( referrer_rewards_value <= reward.amount, "Referrer reward shouldn't be greater than total reward" ); + const asset referrer_reward = recv_asset.amount(referrer_rewards_value); + registrar_reward -= referrer_reward; + deposit_market_fee_vesting_balance(seller.referrer, referrer_reward); + } + deposit_market_fee_vesting_balance(seller.registrar, registrar_reward); + } + } + } + + const auto& recv_dyn_data = recv_asset.dynamic_asset_data_id(*this); + modify( recv_dyn_data, [&issuer_fees, &reward]( asset_dynamic_data_object& obj ){ + obj.accumulated_fees += issuer_fees.amount - reward.amount; + }); + } + + return issuer_fees; +} + } } diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 39ff36ff4f..1347026428 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -256,6 +257,27 @@ struct get_impacted_account_visitor { _impacted.insert( op.fee_payer() ); // account_id } + void operator()( const htlc_create_operation& op ) + { + _impacted.insert( op.fee_payer() ); + _impacted.insert( op.to ); + } + void operator()( const htlc_redeem_operation& op ) + { + _impacted.insert( op.fee_payer() ); + } + void operator()( const htlc_redeemed_operation& op ) + { + _impacted.insert( op.from ); + } + void operator()( const htlc_extend_operation& op ) + { + _impacted.insert( op.fee_payer() ); + } + void operator()( const htlc_refund_operation& op ) + { + _impacted.insert( op.fee_payer() ); + } }; void graphene::chain::operation_get_impacted_accounts( const operation& op, flat_set& result ) @@ -344,6 +366,12 @@ void get_relevant_accounts( const object* obj, flat_set& accoun } case balance_object_type:{ /** these are free from any accounts */ break; + } case htlc_object_type:{ + const auto& htlc_obj = dynamic_cast(obj); + FC_ASSERT( htlc_obj != nullptr ); + accounts.insert( htlc_obj->transfer.from ); + accounts.insert( htlc_obj->transfer.to ); + break; } } } diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 9a5bcad1eb..48dea9fb49 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -191,23 +192,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(); - const auto& call_price_index = call_index.indices().get(); + const call_order_object* call_ptr = nullptr; // place holder for the call order with least collateral ratio - 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 ); + asset_id_type debt_asset_id = mia.id; + auto call_min = price::min( bitasset.options.short_backing_asset, debt_asset_id ); - if( call_itr == call_end ) return false; // no call orders + 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 - price highest = settle_price; + if( before_core_hardfork_1270 ) // before core-1270 hard fork, check with call_price + { + const auto& call_price_index = get_index_type().indices().get(); + 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().indices().get(); + 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; - const auto& dyn_prop = get_dynamic_global_properties(); - auto maint_time = dyn_prop.next_maintenance_time; - if( maint_time > HARDFORK_CORE_338_TIME ) + 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(); const auto& limit_price_index = limit_index.indices().get(); @@ -227,10 +245,10 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s highest = std::max( limit_itr->sell_price, highest ); } - auto least_collateral = call_itr->collateralization(); + auto least_collateral = call_ptr->collateralization(); if( ~least_collateral >= highest ) { - wdump( (*call_itr) ); + 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" @@ -459,6 +477,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().indices().get(); @@ -473,9 +492,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; @@ -558,4 +577,22 @@ void database::update_withdraw_permissions() remove(*permit_index.begin()); } +void database::clear_expired_htlcs() +{ + const auto& htlc_idx = get_index_type().indices().get(); + while ( htlc_idx.begin() != htlc_idx.end() + && htlc_idx.begin()->conditions.time_lock.expiration <= head_block_time() ) + { + const htlc_object& obj = *htlc_idx.begin(); + adjust_balance( obj.transfer.from, asset(obj.transfer.amount, obj.transfer.asset_id) ); + // virtual op + htlc_refund_operation vop( obj.id, obj.transfer.from ); + vop.htlc_id = htlc_idx.begin()->id; + push_applied_operation( vop ); + + // remove the db object + remove( *htlc_idx.begin() ); + } +} + } } diff --git a/libraries/chain/hardfork.d/CORE_1268.hf b/libraries/chain/hardfork.d/CORE_1268.hf new file mode 100644 index 0000000000..352463c043 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_1268.hf @@ -0,0 +1,4 @@ +// #1268 Distribute Asset Market Fees to Referral Program +#ifndef HARDFORK_1268_TIME +#define HARDFORK_1268_TIME (fc::time_point_sec( 1577880000 )) // Wednesday, January 1, 2020 12:00:00 PM +#endif diff --git a/libraries/chain/hardfork.d/CORE_1270.hf b/libraries/chain/hardfork.d/CORE_1270.hf new file mode 100644 index 0000000000..6894635f4e --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_1270.hf @@ -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( 1580000000 )) // a temporary date in the future +#endif diff --git a/libraries/chain/hardfork.d/CORE_1465.hf b/libraries/chain/hardfork.d/CORE_1465.hf new file mode 100644 index 0000000000..90039b9b3d --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_1465.hf @@ -0,0 +1,5 @@ +// 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( 1600000000 )) // 2020-09-13 12:26:40 +#define SOFTFORK_CORE_1465_TIME (fc::time_point_sec( 1545350400 )) // 2018-12-21 00:00:00 +#endif diff --git a/libraries/chain/hardfork.d/CORE_1573.hf b/libraries/chain/hardfork.d/CORE_1573.hf new file mode 100644 index 0000000000..72016a5d7c --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_1573.hf @@ -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( 1600000000 )) +#endif diff --git a/libraries/chain/hardfork.d/CORE_584.hf b/libraries/chain/hardfork.d/CORE_584.hf new file mode 100644 index 0000000000..e570010a94 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_584.hf @@ -0,0 +1,5 @@ +// bitshares-core issue #584 Owner keys of non-immediately required accounts can not authorize a transaction +// https://github.com/bitshares/bitshares-core/issues/584 +#ifndef HARDFORK_CORE_584_TIME +#define HARDFORK_CORE_584_TIME (fc::time_point_sec( 1580000000 )) // a temporary date in the future +#endif diff --git a/libraries/chain/hardfork.d/core-1468.hf b/libraries/chain/hardfork.d/core-1468.hf new file mode 100644 index 0000000000..a669d57c84 --- /dev/null +++ b/libraries/chain/hardfork.d/core-1468.hf @@ -0,0 +1,4 @@ +// HTLC implementation +#ifndef HARDFORK_CORE_1468_TIME +#define HARDFORK_CORE_1468_TIME (fc::time_point_sec( 1600000000 ) ) +#endif diff --git a/libraries/chain/htlc_evaluator.cpp b/libraries/chain/htlc_evaluator.cpp new file mode 100644 index 0000000000..9bd71e630b --- /dev/null +++ b/libraries/chain/htlc_evaluator.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2018 jmjatlanta 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 +#include +#include +#include +#include + +namespace graphene { + namespace chain { + + optional get_committee_htlc_options(graphene::chain::database& db) + { + return db.get_global_properties().parameters.extensions.value.updatable_htlc_options; + } + + void_result htlc_create_evaluator::do_evaluate(const htlc_create_operation& o) + { + graphene::chain::database& d = db(); + optional htlc_options = get_committee_htlc_options(db()); + + FC_ASSERT(htlc_options, "HTLC Committee options are not set."); + + // make sure the expiration is reasonable + FC_ASSERT( o.claim_period_seconds <= htlc_options->max_timeout_secs, "HTLC Timeout exceeds allowed length" ); + // make sure the preimage length is reasonable + FC_ASSERT( o.preimage_size <= htlc_options->max_preimage_size, "HTLC preimage length exceeds allowed length" ); + // make sure the sender has the funds for the HTLC + FC_ASSERT( d.get_balance( o.from, o.amount.asset_id) >= (o.amount), "Insufficient funds") ; + const auto& asset_to_transfer = o.amount.asset_id( d ); + const auto& from_account = o.from( d ); + const auto& to_account = o.to( d ); + FC_ASSERT( is_authorized_asset( d, from_account, asset_to_transfer ), + "Asset ${asset} is not authorized for account ${acct}.", + ( "asset", asset_to_transfer.id )( "acct", from_account.id ) ); + FC_ASSERT( is_authorized_asset( d, to_account, asset_to_transfer ), + "Asset ${asset} is not authorized for account ${acct}.", + ( "asset", asset_to_transfer.id )( "acct", to_account.id ) ); + return void_result(); + } + + object_id_type htlc_create_evaluator::do_apply(const htlc_create_operation& o) + { + try { + graphene::chain::database& dbase = db(); + dbase.adjust_balance( o.from, -o.amount ); + + const htlc_object& esc = db().create([&dbase,&o]( htlc_object& esc ) { + esc.transfer.from = o.from; + esc.transfer.to = o.to; + esc.transfer.amount = o.amount.amount; + esc.transfer.asset_id = o.amount.asset_id; + esc.conditions.hash_lock.preimage_hash = o.preimage_hash; + esc.conditions.hash_lock.preimage_size = o.preimage_size; + esc.conditions.time_lock.expiration = dbase.head_block_time() + o.claim_period_seconds; + }); + return esc.id; + + } FC_CAPTURE_AND_RETHROW( (o) ) + } + + class htlc_redeem_visitor + { + //private: + const std::vector& data; + public: + typedef bool result_type; + + htlc_redeem_visitor( const std::vector& preimage ) + : data( preimage ) {} + + template + bool operator()( const T& preimage_hash )const + { + return T::hash( (const char*)data.data(), (uint32_t) data.size() ) == preimage_hash; + } + }; + + void_result htlc_redeem_evaluator::do_evaluate(const htlc_redeem_operation& o) + { + htlc_obj = &db().get(o.htlc_id); + + FC_ASSERT(o.preimage.size() == htlc_obj->conditions.hash_lock.preimage_size, "Preimage size mismatch."); + + const htlc_redeem_visitor vtor( o.preimage ); + FC_ASSERT( htlc_obj->conditions.hash_lock.preimage_hash.visit( vtor ), "Provided preimage does not generate correct hash."); + + return void_result(); + } + + void_result htlc_redeem_evaluator::do_apply(const htlc_redeem_operation& o) + { + db().adjust_balance(htlc_obj->transfer.to, asset(htlc_obj->transfer.amount, htlc_obj->transfer.asset_id) ); + // notify related parties + htlc_redeemed_operation virt_op( htlc_obj->id, htlc_obj->transfer.from, htlc_obj->transfer.to, + asset(htlc_obj->transfer.amount, htlc_obj->transfer.asset_id ) ); + db().push_applied_operation( virt_op ); + db().remove(*htlc_obj); + return void_result(); + } + + void_result htlc_extend_evaluator::do_evaluate(const htlc_extend_operation& o) + { + htlc_obj = &db().get(o.htlc_id); + return void_result(); + } + + void_result htlc_extend_evaluator::do_apply(const htlc_extend_operation& o) + { + db().modify(*htlc_obj, [&o](htlc_object& db_obj) { + db_obj.conditions.time_lock.expiration += o.seconds_to_add; + }); + + return void_result(); + } + + } // namespace chain +} // namespace graphene diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 5f73e79ddd..38081dc40f 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -191,6 +191,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; @@ -241,7 +244,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 @@ -305,6 +320,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) diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index fdcd3312f8..aee67ff74d 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -121,7 +121,7 @@ #define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT 4 #define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT 3 -#define GRAPHENE_CURRENT_DB_VERSION "20190219" +#define GRAPHENE_CURRENT_DB_VERSION "20190323" #define GRAPHENE_IRREVERSIBLE_THRESHOLD (70 * GRAPHENE_1_PERCENT) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 7fa190cb66..437c50d8dd 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -47,6 +47,7 @@ namespace graphene { namespace chain { class transaction_evaluation_state; struct budget_record; + enum class vesting_balance_type; /** * @class database @@ -301,6 +302,15 @@ namespace graphene { namespace chain { */ void adjust_balance(account_id_type account, asset delta); + void deposit_market_fee_vesting_balance(const account_id_type &account_id, const asset &delta); + /** + * @brief Retrieve a particular account's market fee vesting balance in a given asset + * @param owner Account whose balance should be retrieved + * @param asset_id ID of the asset to get balance in + * @return owner's balance in asset + */ + asset get_market_fee_vesting_balance(const account_id_type &account_id, const asset_id_type &asset_id); + /** * @brief Helper to make lazy deposit to CDD VBO. * @@ -318,6 +328,7 @@ namespace graphene { namespace chain { const optional< vesting_balance_id_type >& ovbid, share_type amount, uint32_t req_vesting_seconds, + vesting_balance_type balance_type, account_id_type req_owner, bool require_vesting ); @@ -350,8 +361,10 @@ 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, the first parameter is taker, the second is maker. @@ -366,14 +379,17 @@ namespace graphene { namespace chain { ///@{ 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 price& feed_price, const uint16_t maintenance_collateral_ratio, + const optional& maintenance_collateralization ); + ///@} + + /// Matches the two orders, the first parameter is taker, the second is maker. /// @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, const price& fill_price); - ///@} /** * @return true if the order was completely filled and thus freed. @@ -393,6 +409,8 @@ namespace graphene { namespace chain { asset calculate_market_fee(const asset_object& recv_asset, const asset& trade_amount); asset pay_market_fees( const asset_object& recv_asset, const asset& receives ); + asset pay_market_fees( const account_object& seller, const asset_object& recv_asset, const asset& receives ); + ///@} ///@{ @@ -488,6 +506,7 @@ namespace graphene { namespace chain { void update_withdraw_permissions(); bool check_for_blackswan( const asset_object& mia, bool enable_black_swan = true, const asset_bitasset_data_object* bitasset_ptr = nullptr ); + void clear_expired_htlcs(); ///Steps performed only at maintenance intervals ///@{ diff --git a/libraries/chain/include/graphene/chain/htlc_evaluator.hpp b/libraries/chain/include/graphene/chain/htlc_evaluator.hpp new file mode 100644 index 0000000000..08a24b2bdc --- /dev/null +++ b/libraries/chain/include/graphene/chain/htlc_evaluator.hpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018 jmjatlanta 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. + */ +#pragma once +#include + +namespace graphene { + namespace chain { + + class htlc_create_evaluator : public evaluator + { + public: + typedef htlc_create_operation operation_type; + + void_result do_evaluate( const htlc_create_operation& o); + object_id_type do_apply( const htlc_create_operation& o); + }; + + class htlc_redeem_evaluator : public evaluator + { + public: + typedef htlc_redeem_operation operation_type; + + void_result do_evaluate( const htlc_redeem_operation& o); + void_result do_apply( const htlc_redeem_operation& o); + const htlc_object* htlc_obj = nullptr; + }; + + class htlc_extend_evaluator : public evaluator + { + public: + typedef htlc_extend_operation operation_type; + + void_result do_evaluate( const htlc_extend_operation& o); + void_result do_apply( const htlc_extend_operation& o); + const htlc_object* htlc_obj = nullptr; + }; + } // namespace graphene +} // namespace graphene diff --git a/libraries/chain/include/graphene/chain/htlc_object.hpp b/libraries/chain/include/graphene/chain/htlc_object.hpp new file mode 100644 index 0000000000..5d41eda392 --- /dev/null +++ b/libraries/chain/include/graphene/chain/htlc_object.hpp @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2018 jmjatlanta 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. + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace graphene { namespace chain { + + /** + * @brief database object to store HTLCs + * + * This object is stored in the database while an HTLC is active. The HTLC will + * become inactive at expiration or when unlocked via the preimage. + */ + class htlc_object : public graphene::db::abstract_object { + public: + // uniquely identify this object in the database + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = htlc_object_type; + + struct transfer_info { + account_id_type from; + account_id_type to; + share_type amount; + asset_id_type asset_id; + } transfer; + struct condition_info { + struct hash_lock_info { + htlc_hash preimage_hash; + uint16_t preimage_size; + } hash_lock; + struct time_lock_info { + fc::time_point_sec expiration; + } time_lock; + } conditions; + + /**** + * Index helper for timelock + */ + struct timelock_extractor { + typedef fc::time_point_sec result_type; + const result_type& operator()(const htlc_object& o)const { return o.conditions.time_lock.expiration; } + }; + + /***** + * Index helper for from + */ + struct from_extractor { + typedef account_id_type result_type; + const result_type& operator()(const htlc_object& o)const { return o.transfer.from; } + }; + + }; + + struct by_from_id; + struct by_expiration; + typedef multi_index_container< + htlc_object, + indexed_by< + ordered_unique< tag< by_id >, member< object, object_id_type, &object::id > >, + + ordered_unique< tag< by_expiration >, + composite_key< htlc_object, + htlc_object::timelock_extractor, + member< object, object_id_type, &object::id > > >, + + ordered_unique< tag< by_from_id >, + composite_key< htlc_object, + htlc_object::from_extractor, + member< object, object_id_type, &object::id > > > + > + + > htlc_object_index_type; + + typedef generic_index< htlc_object, htlc_object_index_type > htlc_index; + +} } // namespace graphene::chain + +FC_REFLECT( graphene::chain::htlc_object::transfer_info, + (from) (to) (amount) (asset_id) ) +FC_REFLECT( graphene::chain::htlc_object::condition_info::hash_lock_info, + (preimage_hash) (preimage_size) ) +FC_REFLECT( graphene::chain::htlc_object::condition_info::time_lock_info, + (expiration) ) +FC_REFLECT( graphene::chain::htlc_object::condition_info, + (hash_lock)(time_lock) ) +FC_REFLECT_DERIVED( graphene::chain::htlc_object, (graphene::db::object), + (transfer) (conditions) ) diff --git a/libraries/chain/include/graphene/chain/market_evaluator.hpp b/libraries/chain/include/graphene/chain/market_evaluator.hpp index 96a4ac07ed..af66323578 100644 --- a/libraries/chain/include/graphene/chain/market_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/market_evaluator.hpp @@ -87,6 +87,7 @@ 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 diff --git a/libraries/chain/include/graphene/chain/market_object.hpp b/libraries/chain/include/graphene/chain/market_object.hpp index 706d5ed313..168e2b6439 100644 --- a/libraries/chain/include/graphene/chain/market_object.hpp +++ b/libraries/chain/include/graphene/chain/market_object.hpp @@ -136,8 +136,20 @@ class call_order_object : public abstract_object return tmp; } - /// Calculate maximum quantity of debt to cover to satisfy @ref target_collateral_ratio. - share_type get_max_debt_to_cover( price match_price, price feed_price, const uint16_t maintenance_collateral_ratio )const; + /** + * 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& maintenance_collateralization = optional() )const; }; /** diff --git a/libraries/chain/include/graphene/chain/protocol/asset.hpp b/libraries/chain/include/graphene/chain/protocol/asset.hpp index 86d17892ff..b354d5ccac 100644 --- a/libraries/chain/include/graphene/chain/protocol/asset.hpp +++ b/libraries/chain/include/graphene/chain/protocol/asset.hpp @@ -193,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 * @@ -209,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 ) diff --git a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp index 3a045a30c9..9c6fca3c9c 100644 --- a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp @@ -27,6 +27,13 @@ namespace graphene { namespace chain { + struct additional_asset_options + { + fc::optional reward_percent; + fc::optional> whitelist_market_fee_sharing; + }; + typedef extension additional_asset_options_t; + bool is_valid_symbol( const string& symbol ); /** @@ -75,7 +82,7 @@ namespace graphene { namespace chain { * size of description. */ string description; - extensions_type extensions; + additional_asset_options_t extensions; /// Perform internal consistency checks. /// @throws fc::exception if any check fails @@ -535,7 +542,7 @@ FC_REFLECT( graphene::chain::bitasset_options, (extensions) ) - +FC_REFLECT( graphene::chain::additional_asset_options, (reward_percent)(whitelist_market_fee_sharing) ) FC_REFLECT( graphene::chain::asset_create_operation::fee_parameters_type, (symbol3)(symbol4)(long_symbol)(price_per_kbyte) ) FC_REFLECT( graphene::chain::asset_global_settle_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::asset_settle_operation::fee_parameters_type, (fee) ) diff --git a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp index 695d9541ee..dba8281305 100644 --- a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp +++ b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp @@ -30,7 +30,12 @@ namespace graphene { namespace chain { struct fee_schedule; } } namespace graphene { namespace chain { - typedef static_variant<> parameter_extension; + struct htlc_options + { + uint32_t max_timeout_secs; + uint32_t max_preimage_size; + }; + struct chain_parameters { /** using a smart ref breaks the circular dependency created between operations and the fee schedule */ @@ -63,14 +68,30 @@ namespace graphene { namespace chain { uint16_t accounts_per_fee_scale = GRAPHENE_DEFAULT_ACCOUNTS_PER_FEE_SCALE; ///< number of accounts between fee scalings uint8_t account_fee_scale_bitshifts = GRAPHENE_DEFAULT_ACCOUNT_FEE_SCALE_BITSHIFTS; ///< number of times to left bitshift account registration fee at each scaling uint8_t max_authority_depth = GRAPHENE_MAX_SIG_CHECK_DEPTH; - extensions_type extensions; + + struct ext + { + optional< htlc_options > updatable_htlc_options; + }; + + extension extensions; /** defined in fee_schedule.cpp */ void validate()const; + }; } } // graphene::chain +FC_REFLECT( graphene::chain::htlc_options, + (max_timeout_secs) + (max_preimage_size) +) + +FC_REFLECT( graphene::chain::chain_parameters::ext, + (updatable_htlc_options) +) + FC_REFLECT( graphene::chain::chain_parameters, (current_fees) (block_interval) diff --git a/libraries/chain/include/graphene/chain/protocol/fee_schedule.hpp b/libraries/chain/include/graphene/chain/protocol/fee_schedule.hpp index 388dada2b0..54d702c84e 100644 --- a/libraries/chain/include/graphene/chain/protocol/fee_schedule.hpp +++ b/libraries/chain/include/graphene/chain/protocol/fee_schedule.hpp @@ -108,6 +108,46 @@ namespace graphene { namespace chain { } }; + template<> + class fee_helper { + public: + const htlc_create_operation::fee_parameters_type& cget(const flat_set& parameters)const + { + auto itr = parameters.find( htlc_create_operation::fee_parameters_type() ); + if ( itr != parameters.end() ) + return itr->get(); + + static htlc_create_operation::fee_parameters_type htlc_create_operation_fee_dummy; + return htlc_create_operation_fee_dummy; + } + }; + + template<> + class fee_helper { + public: + const htlc_redeem_operation::fee_parameters_type& cget(const flat_set& parameters)const + { + auto itr = parameters.find( htlc_redeem_operation::fee_parameters_type() ); + if ( itr != parameters.end() ) + return itr->get(); + + static htlc_redeem_operation::fee_parameters_type htlc_redeem_operation_fee_dummy; + return htlc_redeem_operation_fee_dummy; + } + }; + template<> + class fee_helper { + public: + const htlc_extend_operation::fee_parameters_type& cget(const flat_set& parameters)const + { + auto itr = parameters.find( htlc_extend_operation::fee_parameters_type() ); + if ( itr != parameters.end() ) + return itr->get(); + + static htlc_extend_operation::fee_parameters_type htlc_extend_operation_fee_dummy; + return htlc_extend_operation_fee_dummy; + } + }; /** * @brief contains all of the parameters necessary to calculate the fee for any operation */ @@ -141,6 +181,12 @@ namespace graphene { namespace chain { { return fee_helper().get(parameters); } + template + const bool exists()const + { + auto itr = parameters.find(typename Operation::fee_parameters_type()); + return itr != parameters.end(); + } /** * @note must be sorted by fee_parameters.which() and have no duplicates diff --git a/libraries/chain/include/graphene/chain/protocol/htlc.hpp b/libraries/chain/include/graphene/chain/protocol/htlc.hpp new file mode 100644 index 0000000000..5e9ab847a8 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/htlc.hpp @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2018 jmjatlanta 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. + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include // std::max + +namespace graphene { + namespace chain { + + typedef fc::ripemd160 htlc_algo_ripemd160; + typedef fc::sha1 htlc_algo_sha1; + typedef fc::sha256 htlc_algo_sha256; + + typedef fc::static_variant< + htlc_algo_ripemd160, + htlc_algo_sha1, + htlc_algo_sha256 + > htlc_hash; + + struct htlc_create_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint64_t fee_per_day = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; + }; + // paid to network + asset fee; + // where the held monies are to come from + account_id_type from; + // where the held monies will go if the preimage is provided + account_id_type to; + // the amount to hold + asset amount; + // the (typed) hash of the preimage + htlc_hash preimage_hash; + // the size of the preimage + uint16_t preimage_size; + // The time the funds will be returned to the source if not claimed + uint32_t claim_period_seconds; + // for future expansion + extensions_type extensions; + + /*** + * @brief Does simple validation of this object + */ + void validate()const; + + /** + * @brief who will pay the fee + */ + account_id_type fee_payer()const { return from; } + + /**** + * @brief calculates the fee to be paid for this operation + */ + share_type calculate_fee(const fee_parameters_type& fee_params)const; + }; + + struct htlc_redeem_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint64_t fee_per_kb = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + // paid to network + asset fee; + // the object we are attempting to update + htlc_id_type htlc_id; + // who is attempting to update the transaction + account_id_type redeemer; + // the preimage (not used if after epoch timeout) + std::vector preimage; + // for future expansion + extensions_type extensions; + + /*** + * @brief Perform obvious checks to validate this object + */ + void validate()const; + + /** + * @brief Who is to pay the fee + */ + account_id_type fee_payer()const { return redeemer; } + + /**** + * @brief calculates the fee to be paid for this operation + */ + share_type calculate_fee(const fee_parameters_type& fee_params)const; + }; + + /** + * virtual op to assist with notifying related parties + */ + struct htlc_redeemed_operation : public base_operation + { + struct fee_parameters_type {}; + + htlc_redeemed_operation() {} + htlc_redeemed_operation( htlc_id_type htlc_id, account_id_type from, account_id_type to, asset amount) : + htlc_id(htlc_id), from(from), to(to), amount(amount) {} + + account_id_type fee_payer()const { return to; } + void validate()const { FC_ASSERT( !"virtual operation" ); } + + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + + htlc_id_type htlc_id; + account_id_type from, to; + asset amount; + asset fee; + }; + + struct htlc_extend_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint64_t fee_per_day = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + // paid to network + asset fee; + // the object we are attempting to update + htlc_id_type htlc_id; + // who is attempting to update the transaction + account_id_type update_issuer; + // how much to add + uint32_t seconds_to_add; + // for future expansion + extensions_type extensions; + + /*** + * @brief Perform obvious checks to validate this object + */ + void validate()const; + + /** + * @brief Who is to pay the fee + */ + account_id_type fee_payer()const { return update_issuer; } + + /**** + * @brief calculates the fee to be paid for this operation + */ + share_type calculate_fee(const fee_parameters_type& fee_params)const; + }; + + struct htlc_refund_operation : public base_operation + { + struct fee_parameters_type {}; + + htlc_refund_operation(){} + htlc_refund_operation( const htlc_id_type& htlc_id, const account_id_type& to ) + : htlc_id(htlc_id), to(to) {} + + account_id_type fee_payer()const { return to; } + 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; } + + htlc_id_type htlc_id; + account_id_type to; + asset fee; + }; + } +} + +FC_REFLECT_TYPENAME( graphene::chain::htlc_hash ) + +FC_REFLECT( graphene::chain::htlc_create_operation::fee_parameters_type, (fee) (fee_per_day) ) +FC_REFLECT( graphene::chain::htlc_redeem_operation::fee_parameters_type, (fee) (fee_per_kb) ) +FC_REFLECT( graphene::chain::htlc_redeemed_operation::fee_parameters_type, ) // VIRTUAL +FC_REFLECT( graphene::chain::htlc_extend_operation::fee_parameters_type, (fee) (fee_per_day)) +FC_REFLECT( graphene::chain::htlc_refund_operation::fee_parameters_type, ) // VIRTUAL + +FC_REFLECT( graphene::chain::htlc_create_operation, + (fee)(from)(to)(amount)(preimage_hash)(preimage_size)(claim_period_seconds)(extensions)) +FC_REFLECT( graphene::chain::htlc_redeem_operation, (fee)(htlc_id)(redeemer)(preimage)(extensions)) +FC_REFLECT( graphene::chain::htlc_redeemed_operation, (fee)(htlc_id)(from)(to)(amount) ) +FC_REFLECT( graphene::chain::htlc_extend_operation, (fee)(htlc_id)(update_issuer)(seconds_to_add)(extensions)) +FC_REFLECT( graphene::chain::htlc_refund_operation, (fee)(htlc_id)(to)) diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index de2cfa7fd9..9b5ffabb37 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -38,6 +38,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -95,7 +96,12 @@ namespace graphene { namespace chain { bid_collateral_operation, execute_bid_operation, // VIRTUAL asset_claim_pool_operation, - asset_update_issuer_operation + asset_update_issuer_operation, + htlc_create_operation, + htlc_redeem_operation, + htlc_redeemed_operation, // VIRTUAL + htlc_extend_operation, + htlc_refund_operation // VIRTUAL > operation; /// @} // operations group diff --git a/libraries/chain/include/graphene/chain/protocol/transaction.hpp b/libraries/chain/include/graphene/chain/protocol/transaction.hpp index 84234afb9e..3d1e81c461 100644 --- a/libraries/chain/include/graphene/chain/protocol/transaction.hpp +++ b/libraries/chain/include/graphene/chain/protocol/transaction.hpp @@ -111,7 +111,11 @@ namespace graphene { namespace chain { return results; } - void get_required_authorities( flat_set& active, flat_set& owner, vector& other )const; + void get_required_authorities( flat_set& active, + flat_set& owner, + vector& other )const; + + virtual uint64_t get_packed_size()const; protected: // Calculate the digest used for signature validation @@ -146,13 +150,27 @@ namespace graphene { namespace chain { const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH )const; + /** + * Checks whether signatures in this signed transaction are sufficient to authorize the transaction. + * Throws an exception when failed. + * + * @param chain_id the ID of a block chain + * @param get_active callback function to retrieve active authorities of a given account + * @param get_owner callback function to retrieve owner authorities of a given account + * @param allow_non_immediate_owner whether to allow owner authority of non-immediately + * required accounts to authorize operations in the transaction + * @param max_recursion maximum level of recursion when verifying, since an account + * can have another account in active authorities and/or owner authorities + */ void verify_authority( const chain_id_type& chain_id, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH )const; /** @@ -161,12 +179,12 @@ namespace graphene { namespace chain { * some cases where get_required_signatures() returns a * non-minimal set. */ - set minimize_required_signatures( const chain_id_type& chain_id, const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH ) const; @@ -209,13 +227,32 @@ namespace graphene { namespace chain { virtual const transaction_id_type& id()const override; virtual void validate()const override; virtual const flat_set& get_signature_keys( const chain_id_type& chain_id )const override; + virtual uint64_t get_packed_size()const override; protected: mutable bool _validated = false; + mutable uint64_t _packed_size = 0; }; + /** + * Checks whether given public keys and approvals are sufficient to authorize given operations. + * Throws an exception when failed. + * + * @param ops a vector of operations + * @param sigs a set of public keys + * @param get_active callback function to retrieve active authorities of a given account + * @param get_owner callback function to retrieve owner authorities of a given account + * @param allow_non_immediate_owner whether to allow owner authority of non-immediately + * required accounts to authorize operations + * @param max_recursion maximum level of recursion when verifying, since an account + * can have another account in active authorities and/or owner authorities + * @param allow_committee whether to allow the special "committee account" to authorize the operations + * @param active_approvals accounts that approved the operations with their active authories + * @param owner_approvals accounts that approved the operations with their owner authories + */ void verify_authority( const vector& ops, const flat_set& sigs, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH, bool allow_committe = false, const flat_set& active_aprovals = flat_set(), diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index 4456d6d3a5..87536f42ee 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -36,6 +36,22 @@ #include +// TODO: move this to fc +#include +namespace fc { namespace raw { + template + inline void pack( T& ds, const fc::sha1& ep, uint32_t _max_depth = 1 ) { + ds << ep; + } + + template + inline void unpack( T& ds, sha1& ep, uint32_t _max_depth = 1 ) { + ds >> ep; + } + +} } +// /TODO: move to fc + #include #include #include @@ -140,6 +156,7 @@ namespace graphene { namespace chain { vesting_balance_object_type, worker_object_type, balance_object_type, + htlc_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -182,6 +199,7 @@ namespace graphene { namespace chain { class worker_object; class balance_object; class blinded_balance_object; + class htlc_object; typedef object_id< protocol_ids, account_object_type, account_object> account_id_type; typedef object_id< protocol_ids, asset_object_type, asset_object> asset_id_type; @@ -197,6 +215,7 @@ namespace graphene { namespace chain { typedef object_id< protocol_ids, vesting_balance_object_type, vesting_balance_object> vesting_balance_id_type; typedef object_id< protocol_ids, worker_object_type, worker_object> worker_id_type; typedef object_id< protocol_ids, balance_object_type, balance_object> balance_id_type; + typedef object_id< protocol_ids, htlc_object_type, htlc_object> htlc_id_type; // implementation types class global_property_object; @@ -353,6 +372,7 @@ FC_REFLECT_ENUM( graphene::chain::object_type, (vesting_balance_object_type) (worker_object_type) (balance_object_type) + (htlc_object_type) (OBJECT_TYPE_COUNT) ) FC_REFLECT_ENUM( graphene::chain::impl_object_type, @@ -406,6 +426,7 @@ FC_REFLECT_TYPENAME( graphene::chain::special_authority_id_type ) FC_REFLECT_TYPENAME( graphene::chain::buyback_id_type ) FC_REFLECT_TYPENAME( graphene::chain::fba_accumulator_id_type ) FC_REFLECT_TYPENAME( graphene::chain::collateral_bid_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::htlc_id_type ) FC_REFLECT( graphene::chain::void_t, ) diff --git a/libraries/chain/include/graphene/chain/protocol/vesting.hpp b/libraries/chain/include/graphene/chain/protocol/vesting.hpp index 4915b62ec6..b5d03585de 100644 --- a/libraries/chain/include/graphene/chain/protocol/vesting.hpp +++ b/libraries/chain/include/graphene/chain/protocol/vesting.hpp @@ -42,8 +42,15 @@ namespace graphene { namespace chain { cdd_vesting_policy_initializer( uint32_t vest_sec = 0, fc::time_point_sec sc = fc::time_point_sec() ):start_claim(sc),vesting_seconds(vest_sec){} }; - typedef fc::static_variant vesting_policy_initializer; + struct instant_vesting_policy_initializer + { + }; + typedef fc::static_variant< + linear_vesting_policy_initializer, + cdd_vesting_policy_initializer, + instant_vesting_policy_initializer + > vesting_policy_initializer; /** @@ -117,4 +124,5 @@ FC_REFLECT( graphene::chain::vesting_balance_withdraw_operation, (fee)(vesting_b FC_REFLECT(graphene::chain::linear_vesting_policy_initializer, (begin_timestamp)(vesting_cliff_seconds)(vesting_duration_seconds) ) FC_REFLECT(graphene::chain::cdd_vesting_policy_initializer, (start_claim)(vesting_seconds) ) +FC_REFLECT_EMPTY( graphene::chain::instant_vesting_policy_initializer ) FC_REFLECT_TYPENAME( graphene::chain::vesting_policy_initializer ) diff --git a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp index 210c6c5870..ec3ab0c850 100644 --- a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp +++ b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp @@ -26,6 +26,9 @@ #include #include #include +#include +#include +#include #include #include @@ -119,11 +122,34 @@ namespace graphene { namespace chain { void on_withdraw(const vesting_policy_context& ctx); }; + /** + * @brief instant vesting policy + * + * This policy allows to withdraw everything that is on a balance immediately + * + */ + struct instant_vesting_policy + { + asset get_allowed_withdraw(const vesting_policy_context& ctx)const; + bool is_deposit_allowed(const vesting_policy_context& ctx)const; + bool is_deposit_vested_allowed(const vesting_policy_context&)const { return false; } + bool is_withdraw_allowed(const vesting_policy_context& ctx)const; + void on_deposit(const vesting_policy_context& ctx); + void on_deposit_vested(const vesting_policy_context&); + void on_withdraw(const vesting_policy_context& ctx); + }; + typedef fc::static_variant< linear_vesting_policy, - cdd_vesting_policy + cdd_vesting_policy, + instant_vesting_policy > vesting_policy; + enum class vesting_balance_type { unspecified, + cashback, + worker, + witness, + market_fee_sharing }; /** * Vesting balance object is a balance that is locked by the blockchain for a period of time. */ @@ -140,6 +166,8 @@ namespace graphene { namespace chain { asset balance; /// The vesting policy stores details on when funds vest, and controls when they may be withdrawn vesting_policy policy; + /// type of the vesting balance + vesting_balance_type balance_type = vesting_balance_type::unspecified; vesting_balance_object() {} @@ -171,12 +199,75 @@ namespace graphene { namespace chain { * @ingroup object_index */ struct by_account; + // by_vesting_type index MUST NOT be used for iterating because order is not well-defined. + struct by_vesting_type; + +namespace detail { + + /** + Calculate a hash for account_id_type and asset_id. + Use 48 bit value (see object_id.hpp) for account_id and XOR it with 24 bit for asset_id + */ + inline uint64_t vbo_mfs_hash(const account_id_type& account_id, const asset_id_type& asset_id) + { + return (asset_id.instance.value << 40) ^ account_id.instance.value; + } + + /** + * Used as CompatibleHash + Calculate a hash vesting_balance_object + if vesting_balance_object.balance_type is market_fee_sharing + calculate has as vbo_mfs_hash(vesting_balance_object.owner, hash(vbo.balance.asset_id) (see vbo_mfs_hash) + otherwise: hash_value(vesting_balance_object.id); + */ + struct vesting_balance_object_hash + { + uint64_t operator()(const vesting_balance_object& vbo) const + { + if ( vbo.balance_type == vesting_balance_type::market_fee_sharing ) + { + return vbo_mfs_hash(vbo.owner, vbo.balance.asset_id); + } + return hash_value(vbo.id); + } + }; + + /** + * Used as CompatiblePred + * Compares two vesting_balance_objects + * if vesting_balance_object.balance_type is a market_fee_sharing + * compare owners' ids and assets' ids + * otherwise: vesting_balance_object.id + */ + struct vesting_balance_object_equal + { + bool operator() (const vesting_balance_object& lhs, const vesting_balance_object& rhs) const + { + if ( ( lhs.balance_type == vesting_balance_type::market_fee_sharing ) && + ( lhs.balance_type == rhs.balance_type ) && + ( lhs.owner == rhs.owner ) && + ( lhs.balance.asset_id == rhs.balance.asset_id) + ) + { + return true; + } + return ( lhs.id == rhs.id ); + } + }; +} // detail + typedef multi_index_container< vesting_balance_object, indexed_by< - ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, member< object, object_id_type, &object::id > + >, ordered_non_unique< tag, member + >, + hashed_unique< tag, + identity, + detail::vesting_balance_object_hash, + detail::vesting_balance_object_equal > > > vesting_balance_multi_index_type; @@ -201,10 +292,16 @@ FC_REFLECT(graphene::chain::cdd_vesting_policy, (coin_seconds_earned_last_update) ) +FC_REFLECT_EMPTY( graphene::chain::instant_vesting_policy ) + FC_REFLECT_TYPENAME( graphene::chain::vesting_policy ) FC_REFLECT_DERIVED(graphene::chain::vesting_balance_object, (graphene::db::object), (owner) (balance) (policy) + (balance_type) ) + +FC_REFLECT_ENUM( graphene::chain::vesting_balance_type, (unspecified)(cashback)(worker)(witness)(market_fee_sharing) ) + diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 86f29ef434..17d1164362 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -160,8 +160,10 @@ 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( d.get_dynamic_global_properties().next_maintenance_time <= HARDFORK_CORE_834_TIME ) + 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." ); @@ -170,6 +172,29 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope 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); + + /*** + * We have softfork code already in production to prevent exceeding MAX_SUPPLY between 2018-12-21 until HF 1465. + * But we must allow this in replays until 2018-12-21. The HF 1465 code will correct the problem. + * After HF 1465, we MAY be able to remove the cleanup code IF it never executes. We MAY be able to clean + * up the softfork code IF it never executes. We MAY be able to turn the hardfork code into regular code IF + * noone ever attempted this before HF 1465. + */ + if (next_maintenance_time <= SOFTFORK_CORE_1465_TIME) + { + if ( _dynamic_data_obj->current_supply + o.delta_debt.amount > _debt_asset->options.max_supply ) + ilog("Issue 1465... Borrowing and exceeding MAX_SUPPLY. Will be corrected at hardfork time."); + } + else + { + FC_ASSERT( _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 @@ -201,9 +226,8 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope 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; - FC_ASSERT(dynamic_asset.current_supply >= 0); }); } @@ -220,6 +244,9 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope } } + 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().indices().get(); auto itr = call_idx.find( boost::make_tuple(o.funding_account, o.delta_debt.asset_id) ); const call_order_object* call_obj = nullptr; @@ -233,12 +260,15 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope FC_ASSERT( o.delta_collateral.amount > 0, "Delta collateral amount of new debt position should be positive" ); FC_ASSERT( o.delta_debt.amount > 0, "Delta debt amount of new debt position should be positive" ); - call_obj = &d.create( [&o,this]( call_order_object& call ){ + call_obj = &d.create( [&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; }); call_order_id = call_obj->id; @@ -263,11 +293,14 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope old_collateralization = call_obj->collateralization(); old_debt = call_obj->debt; - d.modify( *call_obj, [&o,new_debt,new_collateral,this]( call_order_object& call ){ + d.modify( *call_obj, [&o,new_debt,new_collateral,this,before_core_hardfork_1270]( call_order_object& call ){ call.collateral = new_collateral; call.debt = new_debt; - call.call_price = price::call_price( call.get_debt(), call.get_collateral(), - _bitasset_data->current_feed.maintenance_collateral_ratio ); + if( before_core_hardfork_1270 ) // don't update call_price after core-1270 hard fork + { + call.call_price = price::call_price( call.get_debt(), call.get_collateral(), + _bitasset_data->current_feed.maintenance_collateral_ratio ); + } call.target_collateral_ratio = o.extensions.value.target_collateral_ratio; }); } @@ -290,8 +323,7 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope GRAPHENE_ASSERT( !call_obj, 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) + "Updating call order would trigger a margin call that cannot be fully filled" ); } else @@ -305,9 +337,11 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // aren't in margin call territory, or it may be because there // were no matching orders. In the latter case, we throw. GRAPHENE_ASSERT( + // we know core-583 hard fork is before core-1270 hard fork, it's ok to use call_price here ~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", + // we know core-583 hard fork is before core-1270 hard fork, it's ok to use call_price here ("a", ~call_obj->call_price )("b", _bitasset_data->current_feed.settlement_price) ); } @@ -319,13 +353,13 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // if collateral ratio is not increased or debt is 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->debt <= *old_debt - && call_obj->collateralization() > *old_collateralization ) - || ~call_obj->call_price < _bitasset_data->current_feed.settlement_price, + FC_ASSERT( ( !before_core_hardfork_1270 + && call_obj->collateralization() > _bitasset_data->current_maintenance_collateralization ) + || ( before_core_hardfork_1270 && ~call_obj->call_price < _bitasset_data->current_feed.settlement_price ) + || ( old_collateralization.valid() && call_obj->debt <= *old_debt + && call_obj->collateralization() > *old_collateralization ), "Can only increase collateral ratio without increasing debt if 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_debt", old_debt) ("new_debt", call_obj->debt) ("old_collateralization", old_collateralization) diff --git a/libraries/chain/market_object.cpp b/libraries/chain/market_object.cpp index 993df7924f..0c2e464906 100644 --- a/libraries/chain/market_object.cpp +++ b/libraries/chain/market_object.cpp @@ -25,6 +25,8 @@ #include +#include + using namespace graphene::chain; /* @@ -56,7 +58,8 @@ max_debt_to_cover = max_amount_to_sell * match_price */ share_type call_order_object::get_max_debt_to_cover( price match_price, price feed_price, - const uint16_t maintenance_collateral_ratio )const + const uint16_t maintenance_collateral_ratio, + const optional& 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 ) @@ -65,7 +68,23 @@ share_type call_order_object::get_max_debt_to_cover( price match_price, FC_ASSERT( feed_price.base.asset_id == call_price.base.asset_id && feed_price.quote.asset_id == call_price.quote.asset_id ); - if( call_price > feed_price ) // feed protected. be defensive here, although this should be guaranteed by caller + 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 @@ -73,6 +92,10 @@ share_type call_order_object::get_max_debt_to_cover( price match_price, 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; @@ -113,9 +136,22 @@ share_type call_order_object::get_max_debt_to_cover( price match_price, return debt; FC_ASSERT( to_pay.amount < collateral && to_cover.amount < debt ); - // check collateral ratio after filled, if it's OK, we return - price new_call_price = price::call_price( get_debt() - to_cover, get_collateral() - to_pay, tcr ); - if( new_call_price > feed_price ) + // 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 result_is_good = after_core_hardfork_1270 ? + std::function( [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( [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 @@ -209,8 +245,8 @@ share_type call_order_object::get_max_debt_to_cover( price match_price, return to_cover.amount; FC_ASSERT( to_pay.amount < collateral && to_cover.amount < debt ); - new_call_price = price::call_price( get_debt() - to_cover, get_collateral() - to_pay, tcr ); - if( new_call_price > feed_price ) // good + // Check whether the result is good + if( result_is_good() ) // good { if( to_pay.amount == max_to_pay.amount ) return to_cover.amount; @@ -255,11 +291,11 @@ share_type call_order_object::get_max_debt_to_cover( price match_price, return debt; } - // check + // defensive check FC_ASSERT( to_pay.amount < collateral && to_cover.amount < debt ); - new_call_price = price::call_price( get_debt() - to_cover, get_collateral() - to_pay, tcr ); - if( new_call_price > feed_price ) // good + // Check whether the result is good + if( result_is_good() ) // good return to_cover.amount; } diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 348629d9ba..e867e733d7 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -28,22 +28,37 @@ namespace graphene { namespace chain { +namespace detail { + void check_asset_options_hf_1268(const fc::time_point_sec& block_time, const asset_options& options); + void check_vesting_balance_policy_hf_1268(const fc::time_point_sec& block_time, const vesting_policy_initializer& policy); +} struct proposal_operation_hardfork_visitor { typedef void result_type; + const database& db; const fc::time_point_sec block_time; const fc::time_point_sec next_maintenance_time; - proposal_operation_hardfork_visitor( const fc::time_point_sec bt, const fc::time_point_sec nmt ) - : block_time(bt), next_maintenance_time(nmt) {} + proposal_operation_hardfork_visitor( const database& _db, const fc::time_point_sec bt ) + : db( _db ), block_time(bt), next_maintenance_time( db.get_dynamic_global_properties().next_maintenance_time ) {} template void operator()(const T &v) const {} - // TODO review and cleanup code below after hard fork - // hf_834 void operator()(const graphene::chain::call_order_update_operation &v) const { + + // TODO If this never ASSERTs before HF 1465, it can be removed + FC_ASSERT( block_time < SOFTFORK_CORE_1465_TIME + || block_time > HARDFORK_CORE_1465_TIME + || v.delta_debt.asset_id == asset_id_type(113) // CNY + || v.delta_debt.amount < 0 + || (v.delta_debt.asset_id( db ).bitasset_data_id + && (*(v.delta_debt.asset_id( db ).bitasset_data_id))( db ).is_prediction_market ) + , "Soft fork - preventing proposal with call_order_update!" ); + + // TODO review and cleanup code below after hard fork + // hf_834 if (next_maintenance_time <= HARDFORK_CORE_834_TIME) { FC_ASSERT( !v.extensions.value.target_collateral_ratio.valid(), "Can not set target_collateral_ratio in call_order_update_operation before hardfork 834." ); @@ -55,6 +70,16 @@ struct proposal_operation_hardfork_visitor static const std::locale &loc = std::locale::classic(); FC_ASSERT(isalpha(v.symbol.back(), loc), "Asset ${s} must end with alpha character before hardfork 620", ("s", v.symbol)); } + + detail::check_asset_options_hf_1268(block_time, v.common_options); + } + // hf_1268 + void operator()(const graphene::chain::asset_update_operation &v) const { + detail::check_asset_options_hf_1268(block_time, v.new_options); + } + // hf_1268 + void operator()(const graphene::chain::vesting_balance_create_operation &v) const { + detail::check_vesting_balance_policy_hf_1268(block_time, v.policy); } // hf_199 void operator()(const graphene::chain::asset_update_issuer_operation &v) const { @@ -92,10 +117,37 @@ struct proposal_operation_hardfork_visitor FC_ASSERT(!"Virtual operation"); } } + void operator()(const graphene::chain::committee_member_update_global_parameters_operation &op) const { + if (block_time < HARDFORK_CORE_1468_TIME) { + FC_ASSERT(!op.new_parameters.extensions.value.updatable_htlc_options.valid(), "Unable to set HTLC options before hardfork 1468"); + FC_ASSERT(!op.new_parameters.current_fees->exists()); + FC_ASSERT(!op.new_parameters.current_fees->exists()); + FC_ASSERT(!op.new_parameters.current_fees->exists()); + } + } + void operator()(const graphene::chain::htlc_create_operation &op) const { + FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); + } + void operator()(const graphene::chain::htlc_redeem_operation &op) const { + FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); + } + void operator()(const graphene::chain::htlc_extend_operation &op) const { + FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); + } // loop and self visit in proposals void operator()(const graphene::chain::proposal_create_operation &v) const { + bool already_contains_proposal_update = false; + for (const op_wrapper &op : v.proposed_ops) + { op.op.visit(*this); + // Do not allow more than 1 proposal_update in a proposal + if ( op.op.which() == operation::tag().value ) + { + FC_ASSERT( !already_contains_proposal_update, "At most one proposal update can be nested in a proposal!" ); + already_contains_proposal_update = true; + } + } } }; @@ -138,8 +190,7 @@ void_result proposal_create_evaluator::do_evaluate(const proposal_create_operati // Calling the proposal hardfork visitor const fc::time_point_sec block_time = d.head_block_time(); - const fc::time_point_sec next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; - proposal_operation_hardfork_visitor vtor( block_time, next_maint_time ); + proposal_operation_hardfork_visitor vtor( d, block_time ); vtor( o ); if( block_time < HARDFORK_CORE_214_TIME ) { // cannot be removed after hf, unfortunately diff --git a/libraries/chain/proposal_object.cpp b/libraries/chain/proposal_object.cpp index 7d7884e1c4..47d4d0db4a 100644 --- a/libraries/chain/proposal_object.cpp +++ b/libraries/chain/proposal_object.cpp @@ -22,6 +22,7 @@ * THE SOFTWARE. */ #include +#include #include namespace graphene { namespace chain { @@ -31,10 +32,12 @@ bool proposal_object::is_authorized_to_execute(database& db) const transaction_evaluation_state dry_run_eval(&db); try { + bool allow_non_immediate_owner = ( db.head_block_time() >= HARDFORK_CORE_584_TIME ); verify_authority( proposed_transaction.operations, available_key_approvals, [&]( account_id_type id ){ return &id(db).active; }, [&]( account_id_type id ){ return &id(db).owner; }, + allow_non_immediate_owner, db.get_global_properties().parameters.max_authority_depth, true, /* allow committeee */ available_active_approvals, diff --git a/libraries/chain/protocol/asset.cpp b/libraries/chain/protocol/asset.cpp index 531ea7f6f6..a9c1daf502 100644 --- a/libraries/chain/protocol/asset.cpp +++ b/libraries/chain/protocol/asset.cpp @@ -203,10 +203,11 @@ namespace graphene { namespace chain { * never go to 0 and the debt can never go more than GRAPHENE_MAX_SHARE_SUPPLY * * CR * DEBT/COLLAT or DEBT/(COLLAT/CR) + * + * Note: this function is only used before core-1270 hard fork. */ price price::call_price( const asset& debt, const asset& collateral, uint16_t collateral_ratio) { try { - // TODO replace the calculation with new operator*() and/or operator/(), could be a hardfork change due to edge cases boost::rational swan(debt.amount.value,collateral.amount.value); boost::rational ratio( collateral_ratio, GRAPHENE_COLLATERAL_RATIO_DENOM ); auto cp = swan * ratio; @@ -239,9 +240,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 @@ -258,17 +261,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 { - // TODO replace the calculation with new operator*() and/or operator/(), could be a hardfork change due to edge cases - boost::rational 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 sp( settlement_price.base.amount.value, settlement_price.quote.amount.value ); boost::rational 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( (cp.numerator() >> 1)+(cp.numerator()&1), (cp.denominator() >> 1)+(cp.denominator()&1) ); + cp = boost::rational( (cp.numerator() >> 1)+(cp.numerator()&1), + (cp.denominator() >> 1)+(cp.denominator()&1) ); - return (asset( cp.numerator().convert_to(), settlement_price.base.asset_id ) / asset( cp.denominator().convert_to(), settlement_price.quote.asset_id )); + return ( asset( cp.numerator().convert_to(), settlement_price.base.asset_id ) + / asset( cp.denominator().convert_to(), 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 diff --git a/libraries/chain/protocol/htlc.cpp b/libraries/chain/protocol/htlc.cpp new file mode 100644 index 0000000000..645feb6dbf --- /dev/null +++ b/libraries/chain/protocol/htlc.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018 jmjatlanta 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 + +#define SECONDS_PER_DAY (60 * 60 * 24) + +namespace graphene { namespace chain { + + void htlc_create_operation::validate()const { + FC_ASSERT( fee.amount >= 0, "Fee amount should not be negative" ); + FC_ASSERT( amount.amount > 0, "HTLC amount should be greater than zero" ); + } + + share_type htlc_create_operation::calculate_fee( const fee_parameters_type& fee_params )const + { + uint64_t days = ( claim_period_seconds + SECONDS_PER_DAY - 1 ) / SECONDS_PER_DAY; + // multiply with overflow check + uint64_t per_day_fee = fee_params.fee_per_day * days; + FC_ASSERT( days == 0 || per_day_fee / days == fee_params.fee_per_day, "Fee calculation overflow" ); + return fee_params.fee + per_day_fee; + } + + void htlc_redeem_operation::validate()const { + FC_ASSERT( fee.amount >= 0, "Fee amount should not be negative" ); + } + + share_type htlc_redeem_operation::calculate_fee( const fee_parameters_type& fee_params )const + { + uint64_t kb = ( preimage.size() + 1023 ) / 1024; + uint64_t product = kb * fee_params.fee_per_kb; + FC_ASSERT( kb == 0 || product / kb == fee_params.fee_per_kb, "Fee calculation overflow"); + return fee_params.fee + product; + } + + void htlc_extend_operation::validate()const { + FC_ASSERT( fee.amount >= 0 , "Fee amount should not be negative"); + } + + share_type htlc_extend_operation::calculate_fee( const fee_parameters_type& fee_params )const + { + uint32_t days = ( seconds_to_add + SECONDS_PER_DAY - 1 ) / SECONDS_PER_DAY; + uint64_t per_day_fee = fee_params.fee_per_day * days; + FC_ASSERT( days == 0 || per_day_fee / days == fee_params.fee_per_day, "Fee calculation overflow" ); + return fee_params.fee + per_day_fee; + } +} } diff --git a/libraries/chain/protocol/transaction.cpp b/libraries/chain/protocol/transaction.cpp index b642dea72e..6b4d7f3235 100644 --- a/libraries/chain/protocol/transaction.cpp +++ b/libraries/chain/protocol/transaction.cpp @@ -59,6 +59,11 @@ void transaction::validate() const operation_validate(op); } +uint64_t transaction::get_packed_size() const +{ + return fc::raw::pack_size(*this); +} + const transaction_id_type& transaction::id() const { auto h = digest(); @@ -92,7 +97,9 @@ void transaction::set_reference_block( const block_id_type& reference_block ) ref_block_prefix = reference_block._hash[1]; } -void transaction::get_required_authorities( flat_set& active, flat_set& owner, vector& other )const +void transaction::get_required_authorities( flat_set& active, + flat_set& owner, + vector& other )const { for( const auto& op : operations ) operation_get_required_authorities( op, active, owner, other ); @@ -101,7 +108,6 @@ void transaction::get_required_authorities( flat_set& active, f } - const flat_set empty_keyset; struct sign_state @@ -161,7 +167,7 @@ struct sign_state bool check_authority( account_id_type id ) { if( approved_by.find(id) != approved_by.end() ) return true; - return check_authority( get_active(id) ); + return check_authority( get_active(id) ) || ( allow_non_immediate_owner && check_authority( get_owner(id) ) ); } /** @@ -196,7 +202,8 @@ struct sign_state { if( depth == max_recursion ) continue; - if( check_authority( get_active( a.first ), depth+1 ) ) + if( check_authority( get_active( a.first ), depth+1 ) + || ( allow_non_immediate_owner && check_authority( get_owner( a.first ), depth+1 ) ) ) { approved_by.insert( a.first ); total_weight += a.second; @@ -227,9 +234,16 @@ struct sign_state } sign_state( const flat_set& sigs, - const std::function& a, + const std::function& active, + const std::function& owner, + bool allow_owner, + uint32_t max_recursion_depth = GRAPHENE_MAX_SIG_CHECK_DEPTH, const flat_set& keys = empty_keyset ) - :get_active(a),available_keys(keys) + : get_active(active), + get_owner(owner), + allow_non_immediate_owner(allow_owner), + max_recursion(max_recursion_depth), + available_keys(keys) { for( const auto& key : sigs ) provided_signatures[ key ] = false; @@ -237,17 +251,21 @@ struct sign_state } const std::function& get_active; - const flat_set& available_keys; + const std::function& get_owner; + + const bool allow_non_immediate_owner; + const uint32_t max_recursion; + const flat_set& available_keys; flat_map provided_signatures; flat_set approved_by; - uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH; }; void verify_authority( const vector& ops, const flat_set& sigs, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion_depth, bool allow_committe, const flat_set& active_aprovals, @@ -264,8 +282,7 @@ void verify_authority( const vector& ops, const flat_set signed_transaction::get_required_signatures( const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion_depth )const { flat_set required_active; @@ -329,8 +347,7 @@ set signed_transaction::get_required_signatures( get_required_authorities( required_active, required_owner, other ); const flat_set& signature_keys = get_signature_keys( chain_id ); - sign_state s( signature_keys, get_active, available_keys ); - s.max_recursion = max_recursion_depth; + sign_state s( signature_keys, get_active, get_owner, allow_non_immediate_owner, max_recursion_depth, available_keys ); for( const auto& auth : other ) s.check_authority(&auth); @@ -356,6 +373,7 @@ set signed_transaction::minimize_required_signatures( const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion ) const { @@ -367,7 +385,7 @@ set signed_transaction::minimize_required_signatures( result.erase( k ); try { - graphene::chain::verify_authority( operations, result, get_active, get_owner, max_recursion ); + graphene::chain::verify_authority( operations, result, get_active, get_owner, allow_non_immediate_owner, max_recursion ); continue; // element stays erased if verify_authority is ok } catch( const tx_missing_owner_auth& e ) {} @@ -392,6 +410,13 @@ void precomputable_transaction::validate() const _validated = true; } +uint64_t precomputable_transaction::get_packed_size()const +{ + if( _packed_size == 0 ) + _packed_size = transaction::get_packed_size(); + return _packed_size; +} + const flat_set& precomputable_transaction::get_signature_keys( const chain_id_type& chain_id )const { // Strictly we should check whether the given chain ID is same as the one used to initialize the `signees` field. @@ -405,9 +430,15 @@ void signed_transaction::verify_authority( const chain_id_type& chain_id, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion )const { try { - graphene::chain::verify_authority( operations, get_signature_keys( chain_id ), get_active, get_owner, max_recursion ); + graphene::chain::verify_authority( operations, + get_signature_keys( chain_id ), + get_active, + get_owner, + allow_non_immediate_owner, + max_recursion ); } FC_CAPTURE_AND_RETHROW( (*this) ) } } } // graphene::chain diff --git a/libraries/chain/vesting_balance_evaluator.cpp b/libraries/chain/vesting_balance_evaluator.cpp index ee918fd16d..586045e4e6 100644 --- a/libraries/chain/vesting_balance_evaluator.cpp +++ b/libraries/chain/vesting_balance_evaluator.cpp @@ -26,8 +26,20 @@ #include #include #include +#include namespace graphene { namespace chain { +namespace detail { + // TODO review and remove code below and links to it after hf_1268 + void check_vesting_balance_policy_hf_1268(const fc::time_point_sec& block_time, const vesting_policy_initializer& policy) + { + if( block_time < HARDFORK_1268_TIME ) + { + FC_ASSERT( policy.which() != vesting_policy_initializer::tag::value, + "Instant vesting policy is only available after HARDFORK_1268_TIME!"); + } + } +} void_result vesting_balance_create_evaluator::do_evaluate( const vesting_balance_create_operation& op ) { try { @@ -42,6 +54,8 @@ void_result vesting_balance_create_evaluator::do_evaluate( const vesting_balance FC_ASSERT( d.get_balance( creator_account.id, op.amount.asset_id ) >= op.amount ); FC_ASSERT( !op.amount.asset_id(d).is_transfer_restricted() ); + detail::check_vesting_balance_policy_hf_1268(d.head_block_time(), op.policy); + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -76,6 +90,12 @@ struct init_policy_visitor policy.coin_seconds_earned_last_update = now; p = policy; } + + void operator()( const instant_vesting_policy_initializer& i )const + { + p = instant_vesting_policy{}; + } + }; object_id_type vesting_balance_create_evaluator::do_apply( const vesting_balance_create_operation& op ) diff --git a/libraries/chain/vesting_balance_object.cpp b/libraries/chain/vesting_balance_object.cpp index 73448e04c8..8735a674f5 100644 --- a/libraries/chain/vesting_balance_object.cpp +++ b/libraries/chain/vesting_balance_object.cpp @@ -157,6 +157,36 @@ bool cdd_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)c return (ctx.amount <= get_allowed_withdraw(ctx)); } +asset instant_vesting_policy::get_allowed_withdraw( const vesting_policy_context& ctx )const +{ + return ctx.balance; +} + +void instant_vesting_policy::on_deposit(const vesting_policy_context& ctx) +{ +} + +void instant_vesting_policy::on_deposit_vested(const vesting_policy_context&) +{ + +} + +bool instant_vesting_policy::is_deposit_allowed(const vesting_policy_context& ctx)const +{ + return (ctx.amount.asset_id == ctx.balance.asset_id) + && sum_below_max_shares(ctx.amount, ctx.balance); +} + +void instant_vesting_policy::on_withdraw(const vesting_policy_context& ctx) +{ +} + +bool instant_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)const +{ + return (ctx.amount.asset_id == ctx.balance.asset_id) + && (ctx.amount <= get_allowed_withdraw(ctx)); +} + #define VESTING_VISITOR(NAME, MAYBE_CONST) \ struct NAME ## _visitor \ { \ diff --git a/libraries/chain/worker_evaluator.cpp b/libraries/chain/worker_evaluator.cpp index b5aea8f3b4..240f9723fa 100644 --- a/libraries/chain/worker_evaluator.cpp +++ b/libraries/chain/worker_evaluator.cpp @@ -58,6 +58,7 @@ struct worker_init_visitor w.balance = db.create([&](vesting_balance_object& b) { b.owner = worker.worker_account; b.balance = asset(0); + b.balance_type = vesting_balance_type::worker; cdd_vesting_policy policy; policy.vesting_seconds = fc::days(i.pay_vesting_period_days).to_seconds(); diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index ca13357f0c..8ec275c60f 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -23,6 +23,8 @@ */ #pragma once +#include +#include #include #include @@ -490,6 +492,13 @@ class wallet_api */ asset_bitasset_data_object get_bitasset_data(string asset_name_or_id)const; + /** + * Returns information about the given HTLC object. + * @param htlc_id the id of the HTLC object. + * @returns the information about the HTLC object + */ + fc::optional get_htlc(string htlc_id) const; + /** Lookup the id of a named account. * @param account_name_or_id the name of the account to look up * @returns the id of the named account @@ -1448,6 +1457,44 @@ class wallet_api bool broadcast = false ); + /** + * Create a hashed time lock contract + * + * @param source The account that will reserve the funds (and pay the fee) + * @param destination The account that will receive the funds if the preimage is presented + * @param amount the amount of the asset that is to be traded + * @param asset_symbol The asset that is to be traded + * @param hash_algorithm the algorithm used to generate the hash from the preimage. Can be RIPEMD160, SHA1 or SHA256. + * @param preimage_hash the hash of the preimage + * @param preimage_size the size of the preimage in bytes + * @param claim_period_seconds how long after creation until the lock expires + * @param broadcast true if you wish to broadcast the transaction + */ + signed_transaction htlc_create( string source, string destination, string amount, string asset_symbol, + string hash_algorithm, const std::string& preimage_hash, uint32_t preimage_size, + const uint32_t claim_period_seconds, bool broadcast = false ); + + /**** + * Update a hashed time lock contract + * + * @param htlc_id The object identifier of the HTLC on the blockchain + * @param issuer Who is performing this operation (and paying the fee) + * @param preimage the preimage that should evaluate to the preimage_hash + */ + signed_transaction htlc_redeem( string htlc_id, string issuer, const std::string& preimage, + bool broadcast = false ); + + /***** + * Increase the timelock on an existing HTLC + * + * @param htlc_id The object identifier of the HTLC on the blockchain + * @param issuer Who is performing this operation (and paying the fee) + * @param seconds_to_add how many seconds to add to the existing timelock + * @param broadcast true to broadcast to the network + */ + signed_transaction htlc_extend(string htlc_id, string issuer, const uint32_t seconds_to_add, + bool broadcast = false); + /** * Get information about a vesting balance object. * @@ -1777,6 +1824,7 @@ FC_API( graphene::wallet::wallet_api, (update_asset) (update_asset_issuer) (update_bitasset) + (get_htlc) (update_asset_feed_producers) (publish_asset_feed) (issue_asset) @@ -1798,6 +1846,9 @@ FC_API( graphene::wallet::wallet_api, (update_witness) (create_worker) (update_worker_votes) + (htlc_create) + (htlc_redeem) + (htlc_extend) (get_vesting_balances) (withdraw_vesting) (vote_for_committee_member) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 421b1db55c..462a5b91c5 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -60,10 +60,13 @@ #include #include #include +#include #include +#include #include #include +#include #include #include #include @@ -113,14 +116,16 @@ struct operation_printer ostream& out; const wallet_api_impl& wallet; operation_result result; + operation_history_object hist; std::string fee(const asset& a) const; public: - operation_printer( ostream& out, const wallet_api_impl& wallet, const operation_result& r = operation_result() ) + operation_printer( ostream& out, const wallet_api_impl& wallet, const operation_history_object& obj ) : out(out), wallet(wallet), - result(r) + result(obj.result), + hist(obj) {} typedef std::string result_type; @@ -133,6 +138,8 @@ struct operation_printer std::string operator()(const account_create_operation& op)const; std::string operator()(const account_update_operation& op)const; std::string operator()(const asset_create_operation& op)const; + std::string operator()(const htlc_create_operation& op)const; + std::string operator()(const htlc_redeem_operation& op)const; }; template @@ -258,6 +265,27 @@ struct op_prototype_visitor } }; +class htlc_hash_to_string_visitor +{ +public: + typedef std::string result_type; + + result_type operator()( const fc::ripemd160& hash )const + { + return "RIPEMD160 " + hash.str(); + } + + result_type operator()( const fc::sha1& hash )const + { + return "SHA1 " + hash.str(); + } + + result_type operator()( const fc::sha256& hash )const + { + return "SHA256 " + hash.str(); + } +}; + class wallet_api_impl { public: @@ -651,6 +679,18 @@ class wallet_api_impl return *opt; } + fc::optional get_htlc(string htlc_id) const + { + htlc_id_type id; + fc::from_variant(htlc_id, id); + auto obj = _remote_db->get_objects( { id }).front(); + if ( !obj.is_null() ) + { + return fc::optional(obj.template as(GRAPHENE_MAX_NESTED_OBJECTS)); + } + return fc::optional(); + } + asset_id_type get_asset_id(string asset_symbol_or_id) const { FC_ASSERT( asset_symbol_or_id.size() > 0 ); @@ -1741,6 +1781,95 @@ class wallet_api_impl return sign_transaction( tx, broadcast ); } + static htlc_hash do_hash( const string& algorithm, const std::string& hash ) + { + string name_upper; + std::transform( algorithm.begin(), algorithm.end(), std::back_inserter(name_upper), ::toupper); + if( name_upper == "RIPEMD160" ) + return fc::ripemd160( hash ); + if( name_upper == "SHA256" ) + return fc::sha256( hash ); + if( name_upper == "SHA1" ) + return fc::sha1( hash ); + FC_THROW_EXCEPTION( fc::invalid_arg_exception, "Unknown algorithm '${a}'", ("a",algorithm) ); + } + + signed_transaction htlc_create( string source, string destination, string amount, string asset_symbol, + string hash_algorithm, const std::string& preimage_hash, uint32_t preimage_size, + const uint32_t claim_period_seconds, bool broadcast = false ) + { + try + { + FC_ASSERT( !self.is_locked() ); + fc::optional asset_obj = get_asset(asset_symbol); + FC_ASSERT(asset_obj, "Could not find asset matching ${asset}", ("asset", asset_symbol)); + + htlc_create_operation create_op; + create_op.from = get_account(source).id; + create_op.to = get_account(destination).id; + create_op.amount = asset_obj->amount_from_string(amount); + create_op.claim_period_seconds = claim_period_seconds; + create_op.preimage_hash = do_hash( hash_algorithm, preimage_hash ); + create_op.preimage_size = preimage_size; + + signed_transaction tx; + tx.operations.push_back(create_op); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + return sign_transaction(tx, broadcast); + } FC_CAPTURE_AND_RETHROW( (source)(destination)(amount)(asset_symbol)(hash_algorithm) + (preimage_hash)(preimage_size)(claim_period_seconds)(broadcast) ) + } + + signed_transaction htlc_redeem( string htlc_id, string issuer, const std::vector& preimage, bool broadcast ) + { + try + { + FC_ASSERT( !self.is_locked() ); + fc::optional htlc_obj = get_htlc(htlc_id); + FC_ASSERT(htlc_obj, "Could not find HTLC matching ${htlc}", ("htlc", htlc_id)); + + account_object issuer_obj = get_account(issuer); + + htlc_redeem_operation update_op; + update_op.htlc_id = htlc_obj->id; + update_op.redeemer = issuer_obj.id; + update_op.preimage = preimage; + + signed_transaction tx; + tx.operations.push_back(update_op); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + return sign_transaction(tx, broadcast); + } FC_CAPTURE_AND_RETHROW( (htlc_id)(issuer)(preimage)(broadcast) ) + } + + signed_transaction htlc_extend ( string htlc_id, string issuer, const uint32_t seconds_to_add, bool broadcast) + { + try + { + FC_ASSERT( !self.is_locked() ); + fc::optional htlc_obj = get_htlc(htlc_id); + FC_ASSERT(htlc_obj, "Could not find HTLC matching ${htlc}", ("htlc", htlc_id)); + + account_object issuer_obj = get_account(issuer); + + htlc_extend_operation update_op; + update_op.htlc_id = htlc_obj->id; + update_op.update_issuer = issuer_obj.id; + update_op.seconds_to_add = seconds_to_add; + + signed_transaction tx; + tx.operations.push_back(update_op); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + return sign_transaction(tx, broadcast); + } FC_CAPTURE_AND_RETHROW( (htlc_id)(issuer)(seconds_to_add)(broadcast) ) + } + vector< vesting_balance_object_with_info > get_vesting_balances( string account_name ) { try { fc::optional vbid = maybe_id( account_name ); @@ -2231,7 +2360,7 @@ class wallet_api_impl auto b = _remote_db->get_block_header(i.block_num); FC_ASSERT(b); ss << b->timestamp.to_iso_string() << " "; - i.op.visit(operation_printer(ss, *this, i.result)); + i.op.visit(operation_printer(ss, *this, i)); ss << " \n"; } @@ -2248,7 +2377,7 @@ class wallet_api_impl auto b = _remote_db->get_block_header(i.block_num); FC_ASSERT(b); ss << b->timestamp.to_iso_string() << " "; - i.op.visit(operation_printer(ss, *this, i.result)); + i.op.visit(operation_printer(ss, *this, i)); ss << " \n"; } @@ -2269,7 +2398,7 @@ class wallet_api_impl auto b = _remote_db->get_block_header(i.block_num); FC_ASSERT(b); ss << b->timestamp.to_iso_string() << " "; - i.op.visit(operation_printer(ss, *this, i.result)); + i.op.visit(operation_printer(ss, *this, i)); ss << " transaction_id : "; ss << d.transaction_id.str(); ss << " \n"; @@ -2311,7 +2440,7 @@ class wallet_api_impl { auto r = result.as( GRAPHENE_MAX_NESTED_OBJECTS ); std::stringstream ss; - r.trx.operations[0].visit( operation_printer( ss, *this, operation_result() ) ); + r.trx.operations[0].visit( operation_printer( ss, *this, operation_history_object() ) ); ss << "\n"; for( const auto& out : r.outputs ) { @@ -2324,7 +2453,7 @@ class wallet_api_impl { auto r = result.as( GRAPHENE_MAX_NESTED_OBJECTS ); std::stringstream ss; - r.trx.operations[0].visit( operation_printer( ss, *this, operation_result() ) ); + r.trx.operations[0].visit( operation_printer( ss, *this, operation_history_object() ) ); ss << "\n"; for( const auto& out : r.outputs ) { @@ -2888,6 +3017,41 @@ std::string operation_printer::operator()(const asset_create_operation& op) cons return fee(op.fee); } +std::string operation_printer::operator()(const htlc_redeem_operation& op) const +{ + out << "Redeem HTLC with database id " + << std::to_string(op.htlc_id.space_id) + << "." << std::to_string(op.htlc_id.type_id) + << "." << std::to_string((uint64_t)op.htlc_id.instance) + << " with preimage \""; + for (unsigned char c : op.preimage) + out << c; + out << "\""; + return fee(op.fee); +} + +std::string operation_printer::operator()(const htlc_create_operation& op) const +{ + static htlc_hash_to_string_visitor vtor; + + auto fee_asset = wallet.get_asset( op.fee.asset_id ); + auto to = wallet.get_account( op.to ); + operation_result_printer rprinter(wallet); + std::string database_id = result.visit(rprinter); + + out << "Create HTLC to " << to.name + << " with id " << database_id + << " preimage hash: [" + << op.preimage_hash.visit( vtor ) + << "] (Fee: " << fee_asset.amount_to_pretty_string( op.fee ) << ")"; + // determine if the block that the HTLC is in is before or after LIB + int32_t pending_blocks = hist.block_num - wallet.get_dynamic_global_properties().last_irreversible_block_num; + if (pending_blocks > 0) + out << " (pending " << std::to_string(pending_blocks) << " blocks)"; + + return ""; +} + std::string operation_result_printer::operator()(const void_result& x) const { return ""; @@ -3018,6 +3182,79 @@ uint64_t wallet_api::get_asset_count()const return my->_remote_db->get_asset_count(); } +signed_transaction wallet_api::htlc_create( string source, string destination, string amount, string asset_symbol, + string hash_algorithm, const std::string& preimage_hash, uint32_t preimage_size, + const uint32_t claim_period_seconds, bool broadcast) +{ + return my->htlc_create(source, destination, amount, asset_symbol, hash_algorithm, preimage_hash, preimage_size, + claim_period_seconds, broadcast); +} + +fc::optional wallet_api::get_htlc(std::string htlc_id) const +{ + fc::optional optional_obj = my->get_htlc(htlc_id); + if ( optional_obj.valid() ) + { + const htlc_object& obj = *optional_obj; + // convert to formatted variant + fc::mutable_variant_object transfer; + const auto& from = my->get_account( obj.transfer.from ); + transfer["from"] = from.name; + const auto& to = my->get_account( obj.transfer.to ); + transfer["to"] = to.name; + const auto& asset = my->get_asset( obj.transfer.asset_id ); + transfer["asset"] = asset.symbol; + transfer["amount"] = graphene::app::uint128_amount_to_string( obj.transfer.amount.value, asset.precision ); + class htlc_hash_to_variant_visitor + { + public: + typedef fc::mutable_variant_object result_type; + + result_type operator()(const fc::ripemd160& obj)const + { return convert("RIPEMD160", obj.str()); } + result_type operator()(const fc::sha1& obj)const + { return convert("SHA1", obj.str()); } + result_type operator()(const fc::sha256& obj)const + { return convert("SHA256", obj.str()); } + private: + result_type convert(const std::string& type, const std::string& hash)const + { + fc::mutable_variant_object ret_val; + ret_val["hash_algo"] = type; + ret_val["preimage_hash"] = hash; + return ret_val; + } + }; + static htlc_hash_to_variant_visitor hash_visitor; + fc::mutable_variant_object htlc_lock = obj.conditions.hash_lock.preimage_hash.visit(hash_visitor); + htlc_lock["preimage_size"] = obj.conditions.hash_lock.preimage_size; + fc::mutable_variant_object time_lock; + time_lock["expiration"] = obj.conditions.time_lock.expiration; + time_lock["time_left"] = fc::get_approximate_relative_time_string(obj.conditions.time_lock.expiration); + fc::mutable_variant_object conditions; + conditions["htlc_lock"] = htlc_lock; + conditions["time_lock"] = time_lock; + fc::mutable_variant_object result; + result["transfer"] = transfer; + result["conditions"] = conditions; + return fc::optional(result); + } + return fc::optional(); +} + +signed_transaction wallet_api::htlc_redeem( std::string htlc_id, std::string issuer, const std::string& preimage, + bool broadcast) +{ + + return my->htlc_redeem(htlc_id, issuer, std::vector(preimage.begin(), preimage.end()), broadcast); +} + +signed_transaction wallet_api::htlc_extend ( std::string htlc_id, std::string issuer, const uint32_t seconds_to_add, + bool broadcast) +{ + return my->htlc_extend(htlc_id, issuer, seconds_to_add, broadcast); +} + vector wallet_api::get_account_history(string name, int limit)const { vector result; @@ -3066,7 +3303,7 @@ vector wallet_api::get_account_history(string name, int limit) } } std::stringstream ss; - auto memo = o.op.visit(detail::operation_printer(ss, *my, o.result)); + auto memo = o.op.visit(detail::operation_printer(ss, *my, o)); result.push_back( operation_detail{ memo, ss.str(), o } ); } @@ -3114,7 +3351,7 @@ vector wallet_api::get_relative_account_history( start); for (auto &o : current) { std::stringstream ss; - auto memo = o.op.visit(detail::operation_printer(ss, *my, o.result)); + auto memo = o.op.visit(detail::operation_printer(ss, *my, o)); result.push_back(operation_detail{memo, ss.str(), o}); } if (current.size() < std::min(100, limit)) @@ -3158,7 +3395,7 @@ account_history_operation_detail wallet_api::get_account_history_by_operations( auto current = my->_remote_hist->get_account_history_by_operations(always_id, operation_types, start, min_limit); for (auto& obj : current.operation_history_objs) { std::stringstream ss; - auto memo = obj.op.visit(detail::operation_printer(ss, *my, obj.result)); + auto memo = obj.op.visit(detail::operation_printer(ss, *my, obj)); transaction_id_type transaction_id; auto block = get_block(obj.block_num); diff --git a/programs/build_helpers/member_enumerator.cpp b/programs/build_helpers/member_enumerator.cpp index 53d1169037..9eda6e3ab5 100644 --- a/programs/build_helpers/member_enumerator.cpp +++ b/programs/build_helpers/member_enumerator.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include diff --git a/programs/js_operation_serializer/main.cpp b/programs/js_operation_serializer/main.cpp index db170a0280..097c8616f8 100644 --- a/programs/js_operation_serializer/main.cpp +++ b/programs/js_operation_serializer/main.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/cli/main.cpp b/tests/cli/main.cpp index a2246c2532..b091f298fc 100644 --- a/tests/cli/main.cpp +++ b/tests/cli/main.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #ifdef _WIN32 #ifndef _WIN32_WINNT @@ -48,6 +49,7 @@ #include #include #endif +#include #include @@ -142,13 +144,15 @@ std::shared_ptr start_application(fc::temp_directory /////////// /// Send a block to the db /// @param app the application +/// @param returned_block the signed block /// @returns true on success /////////// -bool generate_block(std::shared_ptr app) { +bool generate_block(std::shared_ptr app, graphene::chain::signed_block& returned_block) +{ try { fc::ecc::private_key committee_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); auto db = app->chain_database(); - auto block_1 = db->generate_block( db->get_slot_time(1), + returned_block = db->generate_block( db->get_slot_time(1), db->get_scheduled_witness(1), committee_key, database::skip_nothing ); @@ -158,6 +162,12 @@ bool generate_block(std::shared_ptr app) { } } +bool generate_block(std::shared_ptr app) +{ + graphene::chain::signed_block returned_block; + return generate_block(app, returned_block); +} + /////////// /// @brief Skip intermediate blocks, and generate a maintenance block /// @param app the application @@ -451,6 +461,12 @@ BOOST_FIXTURE_TEST_CASE( cli_confidential_tx_test, cli_fixture ) { using namespace graphene::wallet; try { + // we need to increase the default max transaction size to run this test. + this->app1->chain_database()->modify( + this->app1->chain_database()->get_global_properties(), + []( global_property_object& p) { + p.parameters.maximum_transaction_size = 8192; + }); std::vector import_txs; BOOST_TEST_MESSAGE("Importing nathan's balance"); @@ -565,3 +581,188 @@ BOOST_FIXTURE_TEST_CASE( account_history_pagination, cli_fixture ) throw; } } + +/////////////////////// +// Start a server and connect using the same calls as the CLI +// Create an HTLC +/////////////////////// +BOOST_AUTO_TEST_CASE( cli_create_htlc ) +{ + using namespace graphene::chain; + using namespace graphene::app; + std::shared_ptr app1; + try { + fc::temp_directory app_dir( graphene::utilities::temp_directory_path() ); + + int server_port_number = 0; + app1 = start_application(app_dir, server_port_number); + // set committee parameters + app1->chain_database()->modify(app1->chain_database()->get_global_properties(), [](global_property_object& p) { + graphene::chain::htlc_options params; + params.max_preimage_size = 1024; + params.max_timeout_secs = 60 * 60 * 24 * 28; + p.parameters.extensions.value.updatable_htlc_options = params; + }); + + // connect to the server + client_connection con(app1, app_dir, server_port_number); + + BOOST_TEST_MESSAGE("Setting wallet password"); + con.wallet_api_ptr->set_password("supersecret"); + con.wallet_api_ptr->unlock("supersecret"); + + // import Nathan account + BOOST_TEST_MESSAGE("Importing nathan key"); + std::vector nathan_keys{"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"}; + BOOST_CHECK_EQUAL(nathan_keys[0], "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"); + BOOST_CHECK(con.wallet_api_ptr->import_key("nathan", nathan_keys[0])); + + BOOST_TEST_MESSAGE("Importing nathan's balance"); + std::vector import_txs = con.wallet_api_ptr->import_balance("nathan", nathan_keys, true); + account_object nathan_acct_before_upgrade = con.wallet_api_ptr->get_account("nathan"); + + // upgrade nathan + BOOST_TEST_MESSAGE("Upgrading Nathan to LTM"); + signed_transaction upgrade_tx = con.wallet_api_ptr->upgrade_account("nathan", true); + account_object nathan_acct_after_upgrade = con.wallet_api_ptr->get_account("nathan"); + + // verify that the upgrade was successful + BOOST_CHECK_PREDICATE( std::not_equal_to(), (nathan_acct_before_upgrade.membership_expiration_date.sec_since_epoch()) + (nathan_acct_after_upgrade.membership_expiration_date.sec_since_epoch()) ); + BOOST_CHECK(nathan_acct_after_upgrade.is_lifetime_member()); + + // Create new asset called BOBCOIN + try + { + graphene::chain::asset_options asset_ops; + asset_ops.max_supply = 1000000; + asset_ops.core_exchange_rate = price(asset(2),asset(1,asset_id_type(1))); + fc::optional bit_opts; + con.wallet_api_ptr->create_asset("nathan", "BOBCOIN", 5, asset_ops, bit_opts, true); + } + catch(exception& e) + { + BOOST_FAIL(e.what()); + } + catch(...) + { + BOOST_FAIL("Unknown exception creating BOBCOIN"); + } + + // create a new account for Alice + { + graphene::wallet::brain_key_info bki = con.wallet_api_ptr->suggest_brain_key(); + BOOST_CHECK(!bki.brain_priv_key.empty()); + signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key(bki.brain_priv_key, "alice", + "nathan", "nathan", true); + // save the private key for this new account in the wallet file + BOOST_CHECK(con.wallet_api_ptr->import_key("alice", bki.wif_priv_key)); + con.wallet_api_ptr->save_wallet_file(con.wallet_filename); + // attempt to give alice some bitsahres + BOOST_TEST_MESSAGE("Transferring bitshares from Nathan to alice"); + signed_transaction transfer_tx = con.wallet_api_ptr->transfer("nathan", "alice", "10000", "1.3.0", + "Here are some CORE token for your new account", true); + } + + // create a new account for Bob + { + graphene::wallet::brain_key_info bki = con.wallet_api_ptr->suggest_brain_key(); + BOOST_CHECK(!bki.brain_priv_key.empty()); + signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key(bki.brain_priv_key, "bob", + "nathan", "nathan", true); + // save the private key for this new account in the wallet file + BOOST_CHECK(con.wallet_api_ptr->import_key("bob", bki.wif_priv_key)); + con.wallet_api_ptr->save_wallet_file(con.wallet_filename); + // attempt to give bob some bitsahres + BOOST_TEST_MESSAGE("Transferring bitshares from Nathan to Bob"); + signed_transaction transfer_tx = con.wallet_api_ptr->transfer("nathan", "bob", "10000", "1.3.0", + "Here are some CORE token for your new account", true); + con.wallet_api_ptr->issue_asset("bob", "5", "BOBCOIN", "Here are your BOBCOINs", true); + } + + + BOOST_TEST_MESSAGE("Alice has agreed to buy 3 BOBCOIN from Bob for 3 BTS. Alice creates an HTLC"); + // create an HTLC + std::string preimage_string = "My Secret"; + fc::sha256 preimage_md = fc::sha256::hash(preimage_string); + std::stringstream ss; + for(size_t i = 0; i < preimage_md.data_size(); i++) + { + char d = preimage_md.data()[i]; + unsigned char uc = static_cast(d); + ss << std::setfill('0') << std::setw(2) << std::hex << (int)uc; + } + std::string hash_str = ss.str(); + BOOST_TEST_MESSAGE("Secret is " + preimage_string + " and hash is " + hash_str); + uint32_t timelock = fc::days(1).to_seconds(); + graphene::chain::signed_transaction result_tx + = con.wallet_api_ptr->htlc_create("alice", "bob", + "3", "1.3.0", "SHA256", hash_str, preimage_string.size(), timelock, true); + + // normally, a wallet would watch block production, and find the transaction. Here, we can cheat: + std::string alice_htlc_id_as_string; + { + BOOST_TEST_MESSAGE("The system is generating a block"); + graphene::chain::signed_block result_block; + BOOST_CHECK(generate_block(app1, result_block)); + + // get the ID: + htlc_id_type htlc_id = result_block.transactions[result_block.transactions.size()-1].operation_results[0].get(); + alice_htlc_id_as_string = (std::string)(object_id_type)htlc_id; + BOOST_TEST_MESSAGE("Alice shares the HTLC ID with Bob. The HTLC ID is: " + alice_htlc_id_as_string); + } + + // Bob can now look over Alice's HTLC, to see if it is what was agreed to. + BOOST_TEST_MESSAGE("Bob retrieves the HTLC Object by ID to examine it."); + auto alice_htlc = con.wallet_api_ptr->get_htlc(alice_htlc_id_as_string); + BOOST_TEST_MESSAGE("The HTLC Object is: " + fc::json::to_pretty_string(alice_htlc)); + + // Bob likes what he sees, so he creates an HTLC, using the info he retrieved from Alice's HTLC + con.wallet_api_ptr->htlc_create("bob", "alice", + "3", "BOBCOIN", "SHA256", hash_str, preimage_string.size(), timelock, true); + + // normally, a wallet would watch block production, and find the transaction. Here, we can cheat: + std::string bob_htlc_id_as_string; + { + BOOST_TEST_MESSAGE("The system is generating a block"); + graphene::chain::signed_block result_block; + BOOST_CHECK(generate_block(app1, result_block)); + + // get the ID: + htlc_id_type htlc_id = result_block.transactions[result_block.transactions.size()-1].operation_results[0].get(); + bob_htlc_id_as_string = (std::string)(object_id_type)htlc_id; + BOOST_TEST_MESSAGE("Bob shares the HTLC ID with Alice. The HTLC ID is: " + bob_htlc_id_as_string); + } + + // Alice can now look over Bob's HTLC, to see if it is what was agreed to: + BOOST_TEST_MESSAGE("Alice retrieves the HTLC Object by ID to examine it."); + auto bob_htlc = con.wallet_api_ptr->get_htlc(bob_htlc_id_as_string); + BOOST_TEST_MESSAGE("The HTLC Object is: " + fc::json::to_pretty_string(bob_htlc)); + + // Alice likes what she sees, so uses her preimage to get her BOBCOIN + { + BOOST_TEST_MESSAGE("Alice uses her preimage to retrieve the BOBCOIN"); + std::string secret = "My Secret"; + con.wallet_api_ptr->htlc_redeem(bob_htlc_id_as_string, "alice", secret, true); + BOOST_TEST_MESSAGE("The system is generating a block"); + BOOST_CHECK(generate_block(app1)); + } + + // TODO: Bob can look at Alice's history to see her preimage + // Bob can use the preimage to retrieve his BTS + { + BOOST_TEST_MESSAGE("Bob uses Alice's preimage to retrieve the BOBCOIN"); + std::string secret = "My Secret"; + con.wallet_api_ptr->htlc_redeem(alice_htlc_id_as_string, "bob", secret, true); + BOOST_TEST_MESSAGE("The system is generating a block"); + BOOST_CHECK(generate_block(app1)); + } + + // wait for everything to finish up + fc::usleep(fc::seconds(1)); + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } + app1->shutdown(); +} diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index d05f34ee02..b58ba374ca 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -23,6 +23,7 @@ */ #include #include +#include #include #include @@ -37,6 +38,7 @@ #include #include #include +#include #include @@ -62,7 +64,7 @@ void clearable_block::clear() _block_id = block_id_type(); } -database_fixture::database_fixture() +database_fixture::database_fixture(const fc::time_point_sec &initial_timestamp) : app(), db( *app.chain_database() ) { try { @@ -82,9 +84,13 @@ database_fixture::database_fixture() boost::program_options::variables_map options; - genesis_state.initial_timestamp = time_point_sec( GRAPHENE_TESTING_GENESIS_TIMESTAMP ); + genesis_state.initial_timestamp = initial_timestamp; + + if(boost::unit_test::framework::current_test_case().p_name.value == "hf_935_test") + genesis_state.initial_active_witnesses = 20; + else + genesis_state.initial_active_witnesses = 10; - genesis_state.initial_active_witnesses = 10; for( unsigned int i = 0; i < genesis_state.initial_active_witnesses; ++i ) { auto name = "init"+fc::to_string(i); @@ -308,6 +314,13 @@ void database_fixture::verify_asset_supplies( const database& db ) BOOST_CHECK_EQUAL(item.first(db).dynamic_asset_data_id(db).current_supply.value, item.second.value); } + // htlc + const auto& htlc_idx = db.get_index_type< htlc_index >().indices().get< by_id >(); + for( auto itr = htlc_idx.begin(); itr != htlc_idx.end(); ++itr ) + { + total_balances[itr->transfer.asset_id] += itr->transfer.amount; + } + for( const asset_object& asset_obj : db.get_index_type().indices() ) { BOOST_CHECK_EQUAL(total_balances[asset_obj.id].value, asset_obj.dynamic_asset_data_id(db).current_supply.value); @@ -398,7 +411,7 @@ account_create_operation database_fixture::make_account( const std::string& name, const account_object& registrar, const account_object& referrer, - uint8_t referrer_percent /* = 100 */, + uint16_t referrer_percent /* = 100 */, public_key_type key /* = public_key_type() */ ) { @@ -464,7 +477,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 = precision; creator.common_options.market_fee_percent = market_fee_percent; if( issuer == GRAPHENE_WITNESS_ACCOUNT ) @@ -532,8 +545,10 @@ const asset_object& database_fixture::create_user_issued_asset( const string& na return db.get(ptx.operation_results[0].get()); } -const asset_object& database_fixture::create_user_issued_asset( const string& name, const account_object& issuer, uint16_t flags, - const price& core_exchange_rate, uint16_t precision) +const asset_object& database_fixture::create_user_issued_asset( const string& name, const account_object& issuer, + uint16_t flags, const price& core_exchange_rate, + uint8_t precision, uint16_t market_fee_percent, + additional_asset_options_t additional_options) { asset_create_operation creator; creator.issuer = issuer.id; @@ -545,6 +560,8 @@ const asset_object& database_fixture::create_user_issued_asset( const string& na creator.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; creator.common_options.flags = flags; creator.common_options.issuer_permissions = flags; + creator.common_options.market_fee_percent = market_fee_percent; + creator.common_options.extensions = std::move(additional_options); trx.operations.clear(); trx.operations.push_back(std::move(creator)); set_expiration( db, trx ); @@ -618,7 +635,7 @@ const account_object& database_fixture::create_account( const string& name, const account_object& registrar, const account_object& referrer, - uint8_t referrer_percent /* = 100 */, + uint16_t referrer_percent /* = 100 (1%)*/, const public_key_type& key /*= public_key_type()*/ ) { @@ -640,7 +657,7 @@ const account_object& database_fixture::create_account( const private_key_type& key, const account_id_type& registrar_id /* = account_id_type() */, const account_id_type& referrer_id /* = account_id_type() */, - uint8_t referrer_percent /* = 100 */ + uint16_t referrer_percent /* = 100 (1%)*/ ) { try @@ -650,6 +667,8 @@ const account_object& database_fixture::create_account( account_create_operation account_create_op; account_create_op.registrar = registrar_id; + account_create_op.referrer = referrer_id; + account_create_op.referrer_percent = referrer_percent; account_create_op.name = name; account_create_op.owner = authority(1234, public_key_type(key.get_public_key()), 1234); account_create_op.active = authority(5678, public_key_type(key.get_public_key()), 5678); @@ -746,6 +765,9 @@ const limit_order_object* database_fixture::create_sell_order( const account_obj const time_point_sec order_expiration, const price& fee_core_exchange_rate ) { + set_expiration( db, trx ); + trx.operations.clear(); + limit_order_create_operation buy_order; buy_order.seller = user.id; buy_order.amount_to_sell = amount; @@ -1143,6 +1165,16 @@ int64_t database_fixture::get_balance( const account_object& account, const asse return db.get_balance(account.get_id(), a.get_id()).amount.value; } +int64_t database_fixture::get_market_fee_reward( account_id_type account_id, asset_id_type asset_id)const +{ + return db.get_market_fee_vesting_balance(account_id, asset_id).amount.value; +} + +int64_t database_fixture::get_market_fee_reward( const account_object& account, const asset_object& asset )const +{ + return get_market_fee_reward(account.get_id(), asset.get_id()); +} + vector< operation_history_object > database_fixture::get_operation_history( account_id_type account_id )const { vector< operation_history_object > result; diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index dac219d69e..41bce5fd8a 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -155,7 +155,7 @@ extern uint32_t GRAPHENE_TESTING_GENESIS_TIMESTAMP; #define ACTOR(name) \ PREP_ACTOR(name) \ - const auto& name = create_account(BOOST_PP_STRINGIZE(name), name ## _public_key); \ + const auto name = create_account(BOOST_PP_STRINGIZE(name), name ## _public_key); \ graphene::chain::account_id_type name ## _id = name.id; (void)name ## _id; #define GET_ACTOR(name) \ @@ -191,8 +191,10 @@ struct database_fixture { optional data_dir; bool skip_key_index_test = false; uint32_t anon_acct_count; + bool hf1270 = false; - database_fixture(); + database_fixture(const fc::time_point_sec &initial_timestamp = + fc::time_point_sec(GRAPHENE_TESTING_GENESIS_TIMESTAMP)); ~database_fixture(); static fc::ecc::private_key generate_private_key(string seed); @@ -225,7 +227,7 @@ struct database_fixture { const std::string& name, const account_object& registrar, const account_object& referrer, - uint8_t referrer_percent = 100, + uint16_t referrer_percent = 100, public_key_type key = public_key_type() ); @@ -289,7 +291,9 @@ struct database_fixture { const account_object& issuer, uint16_t flags, const price& core_exchange_rate = price(asset(1, asset_id_type(1)), asset(1)), - uint16_t precision = 2 /* traditional precision for tests */); + uint8_t precision = 2 /* traditional precision for tests */, + uint16_t market_fee_percent = 0, + additional_asset_options_t options = additional_asset_options_t()); void issue_uia( const account_object& recipient, asset amount ); void issue_uia( account_id_type recipient_id, asset amount ); @@ -302,7 +306,7 @@ struct database_fixture { const string& name, const account_object& registrar, const account_object& referrer, - uint8_t referrer_percent = 100, + uint16_t referrer_percent = 100, const public_key_type& key = public_key_type() ); @@ -311,7 +315,7 @@ struct database_fixture { const private_key_type& key, const account_id_type& registrar_id = account_id_type(), const account_id_type& referrer_id = account_id_type(), - uint8_t referrer_percent = 100 + uint16_t referrer_percent = 100 ); const committee_member_object& create_committee_member( const account_object& owner ); @@ -335,6 +339,10 @@ struct database_fixture { 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() ); void fund_fee_pool( const account_object& from, const asset_object& asset_to_fund, const share_type amount ); + /** + * NOTE: This modifies the database directly. You will probably have to call this each time you + * finish creating a block + */ void enable_fees(); void change_fees( const flat_set< fee_parameters >& new_params, uint32_t new_scale = 0 ); void upgrade_to_lifetime_member( account_id_type account ); @@ -348,6 +356,10 @@ struct database_fixture { void print_joint_market( const string& syma, const string& symb )const; int64_t get_balance( account_id_type account, asset_id_type a )const; int64_t get_balance( const account_object& account, const asset_object& a )const; + + int64_t get_market_fee_reward( account_id_type account, asset_id_type asset )const; + int64_t get_market_fee_reward( const account_object& account, const asset_object& asset )const; + vector< operation_history_object > get_operation_history( account_id_type account_id )const; vector< graphene::market_history::order_history_object > get_market_order_history( asset_id_type a, asset_id_type b )const; }; diff --git a/tests/performance/market_fee_sharing_tests.cpp b/tests/performance/market_fee_sharing_tests.cpp new file mode 100644 index 0000000000..5c431595ea --- /dev/null +++ b/tests/performance/market_fee_sharing_tests.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2018 Bitshares Foundation, 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 + +#include +#include +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; + +BOOST_FIXTURE_TEST_CASE(mfs_performance_test, database_fixture) +{ + try + { + ACTORS((issuer)); + + const unsigned int accounts = 3000; + const unsigned int iterations = 20; + + std::vector registrators; + for (unsigned int i = 0; i < accounts; ++i) + { + auto account = create_account("registrar" + std::to_string(i)); + transfer(committee_account, account.get_id(), asset(1000000)); + upgrade_to_lifetime_member(account); + + registrators.push_back(std::move(account)); + } + + generate_blocks(HARDFORK_1268_TIME); + generate_block(); + + additional_asset_options_t options; + options.value.reward_percent = 2 * GRAPHENE_1_PERCENT; + + const auto usd = create_user_issued_asset( + "USD", + issuer, + charge_market_fee, + price(asset(1, asset_id_type(1)), asset(1)), + 1, + 20 * GRAPHENE_1_PERCENT, + options); + + issue_uia(issuer, usd.amount(iterations * accounts * 2000)); + + std::vector traders; + for (unsigned int i = 0; i < accounts; ++i) + { + std::string name = "account" + std::to_string(i); + auto account = create_account(name, registrators[i], registrators[i], GRAPHENE_1_PERCENT); + transfer(committee_account, account.get_id(), asset(1000000)); + transfer(issuer, account, usd.amount(iterations * 2000)); + + traders.push_back(std::move(account)); + } + + using namespace std::chrono; + + const auto start = high_resolution_clock::now(); + + for (unsigned int i = 0; i < iterations; ++i) + { + for (unsigned int j = 0; j < accounts; ++j) + { + create_sell_order(traders[j], usd.amount(2000), asset(1)); + create_sell_order(traders[accounts - j - 1], asset(1), usd.amount(1000)); + } + } + + const auto end = high_resolution_clock::now(); + + const auto elapsed = duration_cast(end - start); + wlog("Elapsed: ${c} ms", ("c", elapsed.count())); + + for (unsigned int i = 0; i < accounts; ++i) + { + const auto reward = get_market_fee_reward(registrators[i], usd); + BOOST_CHECK_GT(reward, 0); + } + } + FC_LOG_AND_RETHROW() +} diff --git a/tests/tests/authority_tests.cpp b/tests/tests/authority_tests.cpp index bb32f806e9..ce9f1cadc5 100644 --- a/tests/tests/authority_tests.cpp +++ b/tests/tests/authority_tests.cpp @@ -1192,9 +1192,12 @@ BOOST_FIXTURE_TEST_CASE( get_required_signatures_test, database_fixture ) ) -> bool { //wdump( (tx)(available_keys) ); - set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner ); - //wdump( (result_set)(ref_set) ); - return result_set == ref_set; + set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, false ); + set result_set2 = tx.get_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, true ); + //wdump( (result_set)(result_set2)(ref_set) ); + return result_set == ref_set && result_set2 == ref_set; } ; set_auth( well_id, authority( 60, alice_id, 50, bob_id, 50 ) ); @@ -1306,9 +1309,12 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) ) -> bool { //wdump( (tx)(available_keys) ); - set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner ); - //wdump( (result_set)(ref_set) ); - return result_set == ref_set; + set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, false ); + set result_set2 = tx.get_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, true ); + //wdump( (result_set)(result_set2)(ref_set) ); + return result_set == ref_set && result_set2 == ref_set; } ; auto chk_min = [&]( @@ -1318,9 +1324,12 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) ) -> bool { //wdump( (tx)(available_keys) ); - set result_set = tx.minimize_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner ); - //wdump( (result_set)(ref_set) ); - return result_set == ref_set; + set result_set = tx.minimize_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, false ); + set result_set2 = tx.minimize_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, true ); + //wdump( (result_set)(result_set2)(ref_set) ); + return result_set == ref_set && result_set2 == ref_set; } ; set_auth( roco_id, authority( 2, styx_id, 1, thud_id, 2 ) ); @@ -1337,9 +1346,11 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) BOOST_CHECK( chk( tx, { alice_public_key, bob_public_key }, { alice_public_key, bob_public_key } ) ); BOOST_CHECK( chk_min( tx, { alice_public_key, bob_public_key }, { alice_public_key } ) ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ), fc::exception ); sign( tx, alice_private_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); } catch(fc::exception& e) { @@ -1348,15 +1359,42 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) } } +/* + * Active vs Owner https://github.com/bitshares/bitshares-core/issues/584 + * + * All weights and all thresholds are 1, so every single key should be able to sign if within max_depth + * + * Bob --+--(a)--+-- Alice --+--(a)--+-- Daisy --(a/o)-- Daisy_active_key / Daisy_owner_key + * | | | | + * | | | +-- Alice_active_key + * | | | + * | | +--(o)--+-- Cindy --(a/o)-- Cindy_active_key / Cindy_owner_key + * | | | + * | | +-- Alice_owner_key + * | | + * | +-- Bob_active_key + * | + * +--(o)--+-- Edwin --+--(a)--+-- Gavin --(a/o)-- Gavin_active_key / Gavin_owner_key + * | | | + * | | +-- Edwin_active_key + * | | + * | +--(o)--+-- Frank --(a/o)-- Frank_active_key / Frank_owner_key + * | | + * | +-- Edwin_owner_key + * | + * +-- Bob_owner_key + */ BOOST_FIXTURE_TEST_CASE( parent_owner_test, database_fixture ) { try { ACTORS( - (alice)(bob) + (alice)(bob)(cindy)(daisy)(edwin)(frank)(gavin) ); - auto set_auth2 = [&]( + transfer( account_id_type(), bob_id, asset(100000) ); + + auto set_auth = [&]( account_id_type aid, const authority& active, const authority& owner @@ -1372,14 +1410,6 @@ BOOST_FIXTURE_TEST_CASE( parent_owner_test, database_fixture ) PUSH_TX( db, tx, database::skip_transaction_signatures ); } ; - auto set_auth = [&]( - account_id_type aid, - const authority& auth - ) - { - set_auth2( aid, auth, auth ); - } ; - auto get_active = [&]( account_id_type aid ) -> const authority* @@ -1396,22 +1426,60 @@ BOOST_FIXTURE_TEST_CASE( parent_owner_test, database_fixture ) auto chk = [&]( const signed_transaction& tx, + bool after_hf_584, flat_set available_keys, set ref_set ) -> bool { //wdump( (tx)(available_keys) ); - set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner ); + set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, after_hf_584 ); //wdump( (result_set)(ref_set) ); return result_set == ref_set; } ; fc::ecc::private_key alice_active_key = fc::ecc::private_key::regenerate(fc::digest("alice_active")); fc::ecc::private_key alice_owner_key = fc::ecc::private_key::regenerate(fc::digest("alice_owner")); + fc::ecc::private_key bob_active_key = fc::ecc::private_key::regenerate(fc::digest("bob_active")); + fc::ecc::private_key bob_owner_key = fc::ecc::private_key::regenerate(fc::digest("bob_owner")); + fc::ecc::private_key cindy_active_key = fc::ecc::private_key::regenerate(fc::digest("cindy_active")); + fc::ecc::private_key cindy_owner_key = fc::ecc::private_key::regenerate(fc::digest("cindy_owner")); + fc::ecc::private_key daisy_active_key = fc::ecc::private_key::regenerate(fc::digest("daisy_active")); + fc::ecc::private_key daisy_owner_key = fc::ecc::private_key::regenerate(fc::digest("daisy_owner")); + fc::ecc::private_key edwin_active_key = fc::ecc::private_key::regenerate(fc::digest("edwin_active")); + fc::ecc::private_key edwin_owner_key = fc::ecc::private_key::regenerate(fc::digest("edwin_owner")); + fc::ecc::private_key frank_active_key = fc::ecc::private_key::regenerate(fc::digest("frank_active")); + fc::ecc::private_key frank_owner_key = fc::ecc::private_key::regenerate(fc::digest("frank_owner")); + fc::ecc::private_key gavin_active_key = fc::ecc::private_key::regenerate(fc::digest("gavin_active")); + fc::ecc::private_key gavin_owner_key = fc::ecc::private_key::regenerate(fc::digest("gavin_owner")); + public_key_type alice_active_pub( alice_active_key.get_public_key() ); public_key_type alice_owner_pub( alice_owner_key.get_public_key() ); - set_auth2( alice_id, authority( 1, alice_active_pub, 1 ), authority( 1, alice_owner_pub, 1 ) ); - set_auth( bob_id, authority( 1, alice_id, 1 ) ); + public_key_type bob_active_pub( bob_active_key.get_public_key() ); + public_key_type bob_owner_pub( bob_owner_key.get_public_key() ); + public_key_type cindy_active_pub( cindy_active_key.get_public_key() ); + public_key_type cindy_owner_pub( cindy_owner_key.get_public_key() ); + public_key_type daisy_active_pub( daisy_active_key.get_public_key() ); + public_key_type daisy_owner_pub( daisy_owner_key.get_public_key() ); + public_key_type edwin_active_pub( edwin_active_key.get_public_key() ); + public_key_type edwin_owner_pub( edwin_owner_key.get_public_key() ); + public_key_type frank_active_pub( frank_active_key.get_public_key() ); + public_key_type frank_owner_pub( frank_owner_key.get_public_key() ); + public_key_type gavin_active_pub( gavin_active_key.get_public_key() ); + public_key_type gavin_owner_pub( gavin_owner_key.get_public_key() ); + + set_auth( alice_id, authority( 1, alice_active_pub, 1, daisy_id, 1 ), authority( 1, alice_owner_pub, 1, cindy_id, 1 ) ); + set_auth( bob_id, authority( 1, bob_active_pub, 1, alice_id, 1 ), authority( 1, bob_owner_pub, 1, edwin_id, 1 ) ); + + set_auth( cindy_id, authority( 1, cindy_active_pub, 1 ), authority( 1, cindy_owner_pub, 1 ) ); + set_auth( daisy_id, authority( 1, daisy_active_pub, 1 ), authority( 1, daisy_owner_pub, 1 ) ); + + set_auth( edwin_id, authority( 1, edwin_active_pub, 1, gavin_id, 1 ), authority( 1, edwin_owner_pub, 1, frank_id, 1 ) ); + + set_auth( frank_id, authority( 1, frank_active_pub, 1 ), authority( 1, frank_owner_pub, 1 ) ); + set_auth( gavin_id, authority( 1, gavin_active_pub, 1 ), authority( 1, gavin_owner_pub, 1 ) ); + + generate_block(); signed_transaction tx; transfer_operation op; @@ -1419,17 +1487,369 @@ BOOST_FIXTURE_TEST_CASE( parent_owner_test, database_fixture ) op.to = alice_id; op.amount = asset(1); tx.operations.push_back( op ); + set_expiration( db, tx ); // https://github.com/bitshares/bitshares-core/issues/584 - BOOST_CHECK( chk( tx, { alice_owner_pub }, { } ) ); - BOOST_CHECK( chk( tx, { alice_active_pub, alice_owner_pub }, { alice_active_pub } ) ); + // If not allow non-immediate owner to authorize + BOOST_CHECK( chk( tx, false, { alice_owner_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { alice_active_pub }, { alice_active_pub } ) ); + BOOST_CHECK( chk( tx, false, { alice_active_pub, alice_owner_pub }, { alice_active_pub } ) ); + + BOOST_CHECK( chk( tx, false, { bob_owner_pub }, { bob_owner_pub } ) ); + BOOST_CHECK( chk( tx, false, { bob_active_pub }, { bob_active_pub } ) ); + BOOST_CHECK( chk( tx, false, { bob_active_pub, bob_owner_pub }, { bob_active_pub } ) ); + + BOOST_CHECK( chk( tx, false, { cindy_owner_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { cindy_active_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { cindy_active_pub, cindy_owner_pub }, { } ) ); + + BOOST_CHECK( chk( tx, false, { daisy_owner_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { daisy_active_pub }, { daisy_active_pub } ) ); + BOOST_CHECK( chk( tx, false, { daisy_active_pub, daisy_owner_pub }, { daisy_active_pub } ) ); + + BOOST_CHECK( chk( tx, false, { edwin_owner_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { edwin_active_pub }, { edwin_active_pub } ) ); + BOOST_CHECK( chk( tx, false, { edwin_active_pub, edwin_owner_pub }, { edwin_active_pub } ) ); + + BOOST_CHECK( chk( tx, false, { frank_owner_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { frank_active_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { frank_active_pub, frank_owner_pub }, { } ) ); + + BOOST_CHECK( chk( tx, false, { gavin_owner_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { gavin_active_pub }, { gavin_active_pub } ) ); + BOOST_CHECK( chk( tx, false, { gavin_active_pub, gavin_owner_pub }, { gavin_active_pub } ) ); + + // If allow non-immediate owner to authorize + BOOST_CHECK( chk( tx, true, { alice_owner_pub }, { alice_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { alice_active_pub }, { alice_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { alice_active_pub, alice_owner_pub }, { alice_active_pub } ) ); + + BOOST_CHECK( chk( tx, true, { bob_owner_pub }, { bob_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { bob_active_pub }, { bob_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { bob_active_pub, bob_owner_pub }, { bob_active_pub } ) ); + + BOOST_CHECK( chk( tx, true, { cindy_owner_pub }, { cindy_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { cindy_active_pub }, { cindy_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { cindy_active_pub, cindy_owner_pub }, { cindy_active_pub } ) ); + + BOOST_CHECK( chk( tx, true, { daisy_owner_pub }, { daisy_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { daisy_active_pub }, { daisy_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { daisy_active_pub, daisy_owner_pub }, { daisy_active_pub } ) ); + + BOOST_CHECK( chk( tx, true, { edwin_owner_pub }, { edwin_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { edwin_active_pub }, { edwin_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { edwin_active_pub, edwin_owner_pub }, { edwin_active_pub } ) ); + + BOOST_CHECK( chk( tx, true, { frank_owner_pub }, { frank_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { frank_active_pub }, { frank_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { frank_active_pub, frank_owner_pub }, { frank_active_pub } ) ); + + BOOST_CHECK( chk( tx, true, { gavin_owner_pub }, { gavin_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { gavin_active_pub }, { gavin_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { gavin_active_pub, gavin_owner_pub }, { gavin_active_pub } ) ); + sign( tx, alice_owner_key ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, alice_active_key ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, bob_owner_key ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, bob_active_key ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, cindy_owner_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, cindy_active_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, daisy_owner_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, daisy_active_key ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, edwin_owner_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, edwin_active_key ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, frank_owner_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, frank_active_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, gavin_owner_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + sign( tx, gavin_active_key ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); tx.clear_signatures(); + + // proposal tests + auto new_proposal = [&]() -> proposal_id_type { + signed_transaction ptx; + + proposal_create_operation pop; + pop.proposed_ops.emplace_back(op); + pop.fee_paying_account = bob_id; + pop.expiration_time = db.head_block_time() + fc::days(1); + ptx.operations.push_back(pop); + set_expiration( db, ptx ); + sign( ptx, bob_active_key ); + + return PUSH_TX( db, ptx, database::skip_transaction_dupe_check ).operation_results[0].get(); + }; + + auto approve_proposal = [&]( + proposal_id_type proposal, + account_id_type account, + bool approve_with_owner, + fc::ecc::private_key key + ) + { + signed_transaction ptx; + + proposal_update_operation pup; + pup.fee_paying_account = account; + pup.proposal = proposal; + if( approve_with_owner ) + pup.owner_approvals_to_add.insert( account ); + else + pup.active_approvals_to_add.insert( account ); + ptx.operations.push_back(pup); + set_expiration( db, ptx ); + sign( ptx, key ); + PUSH_TX( db, ptx, database::skip_transaction_dupe_check ); + }; + + proposal_id_type pid; + + pid = new_proposal(); + approve_proposal( pid, alice_id, true, alice_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, alice_id, false, alice_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, bob_id, true, bob_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, bob_id, false, bob_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + // Cindy's approval doesn't work + pid = new_proposal(); + approve_proposal( pid, cindy_id, true, cindy_owner_key ); + BOOST_CHECK( db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, cindy_id, false, cindy_active_key ); + BOOST_CHECK( db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, daisy_id, true, daisy_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, daisy_id, false, daisy_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, edwin_id, true, edwin_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, edwin_id, false, edwin_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + // Frank's approval doesn't work + pid = new_proposal(); + approve_proposal( pid, frank_id, true, frank_owner_key ); + BOOST_CHECK( db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, frank_id, false, frank_active_key ); + BOOST_CHECK( db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, gavin_id, true, gavin_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, gavin_id, false, gavin_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + generate_block( database::skip_transaction_dupe_check ); + + // pass the hard fork time + generate_blocks( HARDFORK_CORE_584_TIME, true, database::skip_transaction_dupe_check ); + set_expiration( db, tx ); + + // signing tests + sign( tx, alice_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + sign( tx, alice_active_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, bob_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, bob_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, cindy_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, cindy_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, daisy_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, daisy_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, edwin_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + sign( tx, edwin_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, frank_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, frank_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, gavin_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, gavin_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + // proposal tests + pid = new_proposal(); + approve_proposal( pid, alice_id, true, alice_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, alice_id, false, alice_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, bob_id, true, bob_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, bob_id, false, bob_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, cindy_id, true, cindy_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, cindy_id, false, cindy_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, daisy_id, true, daisy_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, daisy_id, false, daisy_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, edwin_id, true, edwin_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, edwin_id, false, edwin_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, frank_id, true, frank_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, frank_id, false, frank_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, gavin_id, true, gavin_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, gavin_id, false, gavin_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + generate_block( database::skip_transaction_dupe_check ); } catch(fc::exception& e) { @@ -1492,23 +1912,29 @@ BOOST_FIXTURE_TEST_CASE( missing_owner_auth_test, database_fixture ) tx.operations.push_back( op ); // not signed, should throw tx_missing_owner_auth - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner ), + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), + graphene::chain::tx_missing_owner_auth ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ), graphene::chain::tx_missing_owner_auth ); // signed with alice's active key, should throw tx_missing_owner_auth sign( tx, alice_active_key ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner ), + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), + graphene::chain::tx_missing_owner_auth ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ), graphene::chain::tx_missing_owner_auth ); // signed with alice's owner key, should not throw tx.clear_signatures(); sign( tx, alice_owner_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); // signed with both alice's owner key and active key, // it does not throw due to https://github.com/bitshares/bitshares-core/issues/580 sign( tx, alice_active_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); // creating a transaction that needs active permission tx.clear(); @@ -1517,21 +1943,27 @@ BOOST_FIXTURE_TEST_CASE( missing_owner_auth_test, database_fixture ) tx.operations.push_back( op ); // not signed, should throw tx_missing_active_auth - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner ), + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), + graphene::chain::tx_missing_active_auth ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ), graphene::chain::tx_missing_active_auth ); // signed with alice's active key, should not throw sign( tx, alice_active_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); // signed with alice's owner key, should not throw tx.clear_signatures(); sign( tx, alice_owner_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); // signed with both alice's owner key and active key, should throw tx_irrelevant_sig sign( tx, alice_active_key ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner ), + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), + graphene::chain::tx_irrelevant_sig ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ), graphene::chain::tx_irrelevant_sig ); } diff --git a/tests/tests/bitasset_tests.cpp b/tests/tests/bitasset_tests.cpp index 4fc6097650..03062fbe6e 100644 --- a/tests/tests/bitasset_tests.cpp +++ b/tests/tests/bitasset_tests.cpp @@ -453,6 +453,10 @@ BOOST_AUTO_TEST_CASE( hf_890_test ) generate_blocks(HARDFORK_615_TIME, true, skip); // get around Graphene issue #615 feed expiration bug generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip); + auto hf_time = HARDFORK_CORE_868_890_TIME; + if(hf1270) + hf_time = HARDFORK_CORE_1270_TIME; + for( int i=0; i<2; ++i ) { int blocks = 0; @@ -460,7 +464,7 @@ BOOST_AUTO_TEST_CASE( hf_890_test ) if( i == 1 ) // go beyond hard fork { - blocks += generate_blocks(HARDFORK_CORE_868_890_TIME - mi, true, skip); + blocks += generate_blocks(hf_time - mi, true, skip); blocks += generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip); } set_expiration( db, trx ); @@ -526,7 +530,7 @@ BOOST_AUTO_TEST_CASE( hf_890_test ) ba_op.asset_to_update = usd_id; ba_op.issuer = asset_to_update.issuer; ba_op.new_options = asset_to_update.bitasset_data(db).options; - ba_op.new_options.feed_lifetime_sec = HARDFORK_CORE_868_890_TIME.sec_since_epoch() + ba_op.new_options.feed_lifetime_sec = hf_time.sec_since_epoch() - db.head_block_time().sec_since_epoch() + mi + 1800; @@ -542,7 +546,7 @@ BOOST_AUTO_TEST_CASE( hf_890_test ) BOOST_CHECK( db.find( sell_id ) ); // go beyond hard fork - blocks += generate_blocks(HARDFORK_CORE_868_890_TIME - mi, true, skip); + blocks += generate_blocks(hf_time - mi, true, skip); blocks += generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip); } @@ -923,7 +927,7 @@ BOOST_AUTO_TEST_CASE( hf_935_test ) generate_blocks( db.get_dynamic_global_properties().next_maintenance_time, true, skip ); generate_block( skip ); - for( int i = 0; i < 6; ++i ) + for( int i = 0; i < 8; ++i ) { idump( (i) ); int blocks = 0; @@ -939,6 +943,11 @@ BOOST_AUTO_TEST_CASE( hf_935_test ) generate_blocks( HARDFORK_CORE_935_TIME - mi, true, skip ); generate_blocks( db.get_dynamic_global_properties().next_maintenance_time, true, skip ); } + else if( i == 6 ) // go beyond hard fork 1270 + { + generate_blocks( HARDFORK_CORE_1270_TIME - mi, true, skip ); + generate_blocks( db.get_dynamic_global_properties().next_maintenance_time, true, skip ); + } set_expiration( db, trx ); ACTORS( (seller)(borrower)(feedproducer)(feedproducer2)(feedproducer3) ); @@ -1049,7 +1058,7 @@ BOOST_AUTO_TEST_CASE( hf_935_test ) ba_op.asset_to_update = usd_id; ba_op.issuer = asset_to_update.issuer; ba_op.new_options = asset_to_update.bitasset_data(db).options; - ba_op.new_options.feed_lifetime_sec = HARDFORK_CORE_935_TIME.sec_since_epoch() + ba_op.new_options.feed_lifetime_sec = HARDFORK_CORE_1270_TIME.sec_since_epoch() + mi * 3 + 86400 * 2 - db.head_block_time().sec_since_epoch(); trx.operations.push_back(ba_op); @@ -1102,22 +1111,39 @@ BOOST_AUTO_TEST_CASE( hf_935_test ) blocks += generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip); } - // after hard fork 935, the limit order should be filled + // after hard fork 935, the limit order is filled only for the MSSR test + if( db.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_935_TIME && + db.get_dynamic_global_properties().next_maintenance_time <= HARDFORK_CORE_1270_TIME) { // check median BOOST_CHECK( usd_id(db).bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price ); - if( i % 2 == 0 ) // MCR test, median MCR should be 350% - BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).current_feed.maintenance_collateral_ratio, 3500 ); - else // MSSR test, MSSR should be 125% - BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1250 ); - // the limit order should have been filled - // TODO FIXME this test case is failing for MCR test, - // because call_order's call_price didn't get updated after MCR changed - // BOOST_CHECK( !db.find( sell_id ) ); - if( i % 2 == 1 ) // MSSR test - BOOST_CHECK( !db.find( sell_id ) ); + if( i % 2 == 0) { // MCR test, median MCR should be 350% and order will not be filled except when i = 0 + BOOST_CHECK_EQUAL(usd_id(db).bitasset_data(db).current_feed.maintenance_collateral_ratio, 3500); + if( affected_by_hf_343 ) + BOOST_CHECK(!db.find(sell_id)); + else + BOOST_CHECK(db.find(sell_id)); // MCR bug, order still there + } + else { // MSSR test, MSSR should be 125% and order is filled + BOOST_CHECK_EQUAL(usd_id(db).bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1250); + BOOST_CHECK(!db.find(sell_id)); // order filled + } + + // go beyond hard fork 1270 + blocks += generate_blocks(HARDFORK_CORE_1270_TIME - mi, true, skip); + blocks += generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip); } + // after hard fork 1270, the limit order should be filled for MCR test + if( db.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_1270_TIME) + { + // check median + BOOST_CHECK( usd_id(db).bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price ); + if( i % 2 == 0 ) { // MCR test, order filled + BOOST_CHECK_EQUAL(usd_id(db).bitasset_data(db).current_feed.maintenance_collateral_ratio, 3500); + BOOST_CHECK(!db.find(sell_id)); + } + } // undo above tx's and reset generate_block( skip ); @@ -1375,5 +1401,11 @@ BOOST_AUTO_TEST_CASE( reset_backing_asset_switching_to_witness_fed ) } } */ +BOOST_AUTO_TEST_CASE(hf_890_test_hf1270) +{ try { + hf1270 = true; + INVOKE(hf_890_test); + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/database_api_tests.cpp b/tests/tests/database_api_tests.cpp index 1eeb177b42..2db747d0ce 100644 --- a/tests/tests/database_api_tests.cpp +++ b/tests/tests/database_api_tests.cpp @@ -25,6 +25,7 @@ #include #include +#include #include @@ -36,7 +37,8 @@ using namespace graphene::chain::test; BOOST_FIXTURE_TEST_SUITE(database_api_tests, database_fixture) -BOOST_AUTO_TEST_CASE(is_registered) { +BOOST_AUTO_TEST_CASE(is_registered) +{ try { /*** * Arrange @@ -71,7 +73,8 @@ BOOST_AUTO_TEST_CASE(is_registered) { } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE( get_potential_signatures_owner_and_active ) { +BOOST_AUTO_TEST_CASE( get_potential_signatures_owner_and_active ) +{ try { fc::ecc::private_key nathan_key1 = fc::ecc::private_key::regenerate(fc::digest("key1")); fc::ecc::private_key nathan_key2 = fc::ecc::private_key::regenerate(fc::digest("key2")); @@ -118,7 +121,151 @@ BOOST_AUTO_TEST_CASE( get_potential_signatures_owner_and_active ) { } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE( get_potential_signatures_other ) { +/// Testing get_potential_signatures and get_required_signatures for non-immediate owner authority issue. +/// https://github.com/bitshares/bitshares-core/issues/584 +BOOST_AUTO_TEST_CASE( get_signatures_non_immediate_owner ) +{ + try { + fc::ecc::private_key nathan_key1 = fc::ecc::private_key::regenerate(fc::digest("key1")); + fc::ecc::private_key nathan_key2 = fc::ecc::private_key::regenerate(fc::digest("key2")); + fc::ecc::private_key ashley_key1 = fc::ecc::private_key::regenerate(fc::digest("akey1")); + fc::ecc::private_key ashley_key2 = fc::ecc::private_key::regenerate(fc::digest("akey2")); + fc::ecc::private_key oliver_key1 = fc::ecc::private_key::regenerate(fc::digest("okey1")); + fc::ecc::private_key oliver_key2 = fc::ecc::private_key::regenerate(fc::digest("okey2")); + public_key_type pub_key_active( nathan_key1.get_public_key() ); + public_key_type pub_key_owner( nathan_key2.get_public_key() ); + public_key_type a_pub_key_active( ashley_key1.get_public_key() ); + public_key_type a_pub_key_owner( ashley_key2.get_public_key() ); + public_key_type o_pub_key_active( oliver_key1.get_public_key() ); + public_key_type o_pub_key_owner( oliver_key2.get_public_key() ); + const account_object& nathan = create_account("nathan", nathan_key1.get_public_key() ); + const account_object& ashley = create_account("ashley", ashley_key1.get_public_key() ); + const account_object& oliver = create_account("oliver", oliver_key1.get_public_key() ); + account_id_type nathan_id = nathan.id; + account_id_type ashley_id = ashley.id; + account_id_type oliver_id = oliver.id; + + try { + account_update_operation op; + op.account = nathan_id; + op.active = authority(1, pub_key_active, 1, ashley_id, 1); + op.owner = authority(1, pub_key_owner, 1, oliver_id, 1); + trx.operations.push_back(op); + sign(trx, nathan_key1); + PUSH_TX( db, trx, database::skip_transaction_dupe_check ); + trx.clear(); + + op.account = ashley_id; + op.active = authority(1, a_pub_key_active, 1); + op.owner = authority(1, a_pub_key_owner, 1); + trx.operations.push_back(op); + sign(trx, ashley_key1); + PUSH_TX( db, trx, database::skip_transaction_dupe_check ); + trx.clear(); + + op.account = oliver_id; + op.active = authority(1, o_pub_key_active, 1); + op.owner = authority(1, o_pub_key_owner, 1); + trx.operations.push_back(op); + sign(trx, oliver_key1); + PUSH_TX( db, trx, database::skip_transaction_dupe_check ); + trx.clear(); + } FC_CAPTURE_AND_RETHROW ((nathan.active)) + + // this transaction requires active + signed_transaction trx_a; + transfer_operation op; + op.from = nathan_id; + op.to = account_id_type(); + trx_a.operations.push_back(op); + + // get potential signatures + graphene::app::database_api db_api(db); + set pub_keys = db_api.get_potential_signatures( trx_a ); + + BOOST_CHECK( pub_keys.find( pub_key_active ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_active ) != pub_keys.end() ); + // doesn't work due to https://github.com/bitshares/bitshares-core/issues/584 + BOOST_CHECK( pub_keys.find( a_pub_key_owner ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_active ) != pub_keys.end() ); + // doesn't work due to https://github.com/bitshares/bitshares-core/issues/584 + BOOST_CHECK( pub_keys.find( o_pub_key_owner ) == pub_keys.end() ); + + // get required signatures + pub_keys = db_api.get_required_signatures( trx_a, { a_pub_key_owner, o_pub_key_owner } ); + BOOST_CHECK( pub_keys.empty() ); + + // this op requires owner + signed_transaction trx_o; + account_update_operation auop; + auop.account = nathan_id; + auop.owner = authority(1, pub_key_owner, 1); + trx_o.operations.push_back(auop); + + // get potential signatures + pub_keys = db_api.get_potential_signatures( trx_o ); + + // active authorities doesn't help in this case + BOOST_CHECK( pub_keys.find( pub_key_active ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_active ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_owner ) == pub_keys.end() ); + + // owner authorities should be ok + BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_active ) != pub_keys.end() ); + // doesn't work due to https://github.com/bitshares/bitshares-core/issues/584 + BOOST_CHECK( pub_keys.find( o_pub_key_owner ) == pub_keys.end() ); + + // get required signatures + pub_keys = db_api.get_required_signatures( trx_o, { a_pub_key_owner, o_pub_key_owner } ); + BOOST_CHECK( pub_keys.empty() ); + + // go beyond hard fork + generate_blocks( HARDFORK_CORE_584_TIME, true ); + + // for the transaction that requires active + // get potential signatures + pub_keys = db_api.get_potential_signatures( trx_a ); + + // all authorities should be ok + BOOST_CHECK( pub_keys.find( pub_key_active ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_active ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_owner ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_active ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_owner ) != pub_keys.end() ); + + // get required signatures + pub_keys = db_api.get_required_signatures( trx_a, { a_pub_key_owner } ); + BOOST_CHECK( pub_keys.find( a_pub_key_owner ) != pub_keys.end() ); + pub_keys = db_api.get_required_signatures( trx_a, { o_pub_key_owner } ); + BOOST_CHECK( pub_keys.find( o_pub_key_owner ) != pub_keys.end() ); + + // for the transaction that requires owner + // get potential signatures + pub_keys = db_api.get_potential_signatures( trx_o ); + + // active authorities doesn't help in this case + BOOST_CHECK( pub_keys.find( pub_key_active ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_active ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_owner ) == pub_keys.end() ); + + // owner authorities should help + BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_active ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_owner ) != pub_keys.end() ); + + // get required signatures + pub_keys = db_api.get_required_signatures( trx_o, { a_pub_key_owner, o_pub_key_owner } ); + BOOST_CHECK( pub_keys.find( a_pub_key_owner ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_owner ) != pub_keys.end() ); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( get_potential_signatures_other ) +{ try { fc::ecc::private_key priv_key1 = fc::ecc::private_key::regenerate(fc::digest("key1")); public_key_type pub_key1( priv_key1.get_public_key() ); @@ -138,7 +285,8 @@ BOOST_AUTO_TEST_CASE( get_potential_signatures_other ) { } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE( get_required_signatures_owner_or_active ) { +BOOST_AUTO_TEST_CASE( get_required_signatures_owner_or_active ) +{ try { fc::ecc::private_key nathan_key1 = fc::ecc::private_key::regenerate(fc::digest("key1")); fc::ecc::private_key nathan_key2 = fc::ecc::private_key::regenerate(fc::digest("key2")); @@ -211,7 +359,8 @@ BOOST_AUTO_TEST_CASE( get_required_signatures_owner_or_active ) { } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE( get_required_signatures_partially_signed_or_not ) { +BOOST_AUTO_TEST_CASE( get_required_signatures_partially_signed_or_not ) +{ try { fc::ecc::private_key morgan_key = fc::ecc::private_key::regenerate(fc::digest("morgan_key")); fc::ecc::private_key nathan_key = fc::ecc::private_key::regenerate(fc::digest("nathan_key")); @@ -602,7 +751,8 @@ BOOST_AUTO_TEST_CASE( get_required_signatures_partially_signed_or_not ) { } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE( set_subscribe_callback_disable_notify_all_test ) { +BOOST_AUTO_TEST_CASE( set_subscribe_callback_disable_notify_all_test ) +{ try { ACTORS( (alice) ); @@ -792,7 +942,7 @@ BOOST_AUTO_TEST_CASE(get_account_limit_orders) limit_order_id_type(o.id), o.sell_price); BOOST_CHECK(results.size() == 71); BOOST_CHECK(results.front().id > o.id); - // NOTE 3: because of NOTE 1, here should be little than + // NOTE 3: because of NOTE 1, here should be little than BOOST_CHECK(results.front().sell_price < o.sell_price); for (size_t i = 0 ; i < results.size() - 1 ; ++i) { diff --git a/tests/tests/htlc_tests.cpp b/tests/tests/htlc_tests.cpp new file mode 100644 index 0000000000..826cc7e1a2 --- /dev/null +++ b/tests/tests/htlc_tests.cpp @@ -0,0 +1,771 @@ +/* + * Copyright (c) 2018 jmjatlanta 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. + */ + +// below are for random bytes for htlc +#include +#include +#include +#include +#include +// for htlc timeout +#include +#include + +#include + +#include + +#include + +#include + +#include +#include + +#include +#include +#include + +#include + +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( htlc_tests, database_fixture ) + +void generate_random_preimage(uint16_t key_size, std::vector& vec) +{ + std::independent_bits_engine rbe; + std::generate(begin(vec), end(vec), std::ref(rbe)); + return; +} + +/**** + * Hash the preimage and put it in a vector + * @param preimage the preimage + * @returns a vector that cointains the sha256 hash of the preimage + */ +template +H hash_it(std::vector preimage) +{ + return H::hash( (char*)preimage.data(), preimage.size() ); +} + +flat_map< uint64_t, graphene::chain::fee_parameters > get_htlc_fee_parameters() +{ + flat_map ret_val; + + htlc_create_operation::fee_parameters_type create_param; + create_param.fee_per_day = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; + create_param.fee = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; + ret_val[((operation)htlc_create_operation()).which()] = create_param; + + htlc_redeem_operation::fee_parameters_type redeem_param; + redeem_param.fee = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; + redeem_param.fee_per_kb = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; + ret_val[((operation)htlc_redeem_operation()).which()] = redeem_param; + + htlc_extend_operation::fee_parameters_type extend_param; + extend_param.fee = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; + extend_param.fee_per_day = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; + ret_val[((operation)htlc_extend_operation()).which()] = extend_param; + + return ret_val; +} + +/**** + * @brief push through a proposal that sets htlc parameters and fees + * @param db_fixture the database connection + */ +void set_committee_parameters(database_fixture* db_fixture) +{ + // htlc fees + // get existing fee_schedule + const chain_parameters& existing_params = db_fixture->db.get_global_properties().parameters; + const fee_schedule_type& existing_fee_schedule = *(existing_params.current_fees); + // create a new fee_shedule + fee_schedule_type new_fee_schedule; + new_fee_schedule.scale = GRAPHENE_100_PERCENT; + // replace the old with the new + flat_map params_map = get_htlc_fee_parameters(); + for(auto param : existing_fee_schedule.parameters) + { + auto itr = params_map.find(param.which()); + if (itr == params_map.end()) + new_fee_schedule.parameters.insert(param); + else + { + new_fee_schedule.parameters.insert( (*itr).second); + } + } + // htlc parameters + proposal_create_operation cop = proposal_create_operation::committee_proposal( + db_fixture->db.get_global_properties().parameters, db_fixture->db.head_block_time()); + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.expiration_time = db_fixture->db.head_block_time() + *cop.review_period_seconds + 10; + committee_member_update_global_parameters_operation uop; + graphene::chain::htlc_options new_params; + new_params.max_preimage_size = 19200; + new_params.max_timeout_secs = 60 * 60 * 24 * 28; + uop.new_parameters.extensions.value.updatable_htlc_options = new_params; + uop.new_parameters.current_fees = new_fee_schedule; + cop.proposed_ops.emplace_back(uop); + + db_fixture->trx.operations.push_back(cop); + graphene::chain::processed_transaction proc_trx =db_fixture->db.push_transaction(db_fixture->trx); + db_fixture->trx.clear(); + proposal_id_type good_proposal_id = proc_trx.operation_results[0].get(); + + proposal_update_operation puo; + puo.proposal = good_proposal_id; + puo.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + puo.key_approvals_to_add.emplace( db_fixture->init_account_priv_key.get_public_key() ); + db_fixture->trx.operations.push_back(puo); + db_fixture->sign( db_fixture->trx, db_fixture->init_account_priv_key ); + db_fixture->db.push_transaction(db_fixture->trx); + db_fixture->trx.clear(); + + db_fixture->generate_blocks( good_proposal_id( db_fixture->db ).expiration_time + 5 ); + db_fixture->generate_blocks( db_fixture->db.get_dynamic_global_properties().next_maintenance_time ); + db_fixture->generate_block(); // get the maintenance skip slots out of the way + +} + +void advance_past_hardfork(database_fixture* db_fixture) +{ + db_fixture->generate_blocks(HARDFORK_CORE_1468_TIME); + set_expiration(db_fixture->db, db_fixture->trx); + set_committee_parameters(db_fixture); + set_expiration(db_fixture->db, db_fixture->trx); +} + +BOOST_AUTO_TEST_CASE( htlc_expires ) +{ +try { + ACTORS((alice)(bob)); + + int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION); + + transfer( committee_account, alice_id, graphene::chain::asset(init_balance) ); + + advance_past_hardfork(this); + + uint16_t preimage_size = 256; + std::vector pre_image(256); + generate_random_preimage(preimage_size, pre_image); + + graphene::chain::htlc_id_type alice_htlc_id; + // cler everything out + generate_block(); + trx.clear(); + // Alice puts a contract on the blockchain + { + graphene::chain::htlc_create_operation create_operation; + BOOST_TEST_MESSAGE("Alice (who has 100 coins, is transferring 2 coins to Bob"); + create_operation.amount = graphene::chain::asset( 3 * GRAPHENE_BLOCKCHAIN_PRECISION ); + create_operation.to = bob_id; + create_operation.claim_period_seconds = 60; + create_operation.preimage_hash = hash_it( pre_image ); + create_operation.preimage_size = preimage_size; + create_operation.from = alice_id; + create_operation.fee = db.get_global_properties().parameters.current_fees->calculate_fee(create_operation); + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + graphene::chain::signed_block blk = generate_block(); + processed_transaction alice_trx = blk.transactions[0]; + alice_htlc_id = alice_trx.operation_results[0].get(); + generate_block(); + } + + // verify funds on hold... 100 - 3 = 97, minus the 4 coin fee = 93 + BOOST_CHECK_EQUAL( get_balance(alice_id, graphene::chain::asset_id_type()), 93 * GRAPHENE_BLOCKCHAIN_PRECISION ); + + // make sure Bob (or anyone) can see the details of the transaction + graphene::app::database_api db_api(db); + auto obj = db_api.get_objects( {alice_htlc_id }).front(); + graphene::chain::htlc_object htlc = obj.template as(GRAPHENE_MAX_NESTED_OBJECTS); + + // let it expire (wait for timeout) + generate_blocks( db.head_block_time() + fc::seconds(120) ); + // verify funds return (minus the fees) + BOOST_CHECK_EQUAL( get_balance(alice_id, graphene::chain::asset_id_type()), 96 * GRAPHENE_BLOCKCHAIN_PRECISION ); + // verify Bob cannot execute the contract after the fact +} FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( htlc_fulfilled ) +{ +try { + ACTORS((alice)(bob)); + + int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION); + + transfer( committee_account, alice_id, graphene::chain::asset(init_balance) ); + transfer( committee_account, bob_id, graphene::chain::asset(init_balance) ); + + advance_past_hardfork(this); + + uint16_t preimage_size = 256; + std::vector pre_image(preimage_size); + generate_random_preimage(preimage_size, pre_image); + + graphene::chain::htlc_id_type alice_htlc_id; + // clear everything out + generate_block(); + trx.clear(); + // Alice puts a contract on the blockchain + { + graphene::chain::htlc_create_operation create_operation; + + create_operation.amount = graphene::chain::asset( 20 * GRAPHENE_BLOCKCHAIN_PRECISION ); + create_operation.to = bob_id; + create_operation.claim_period_seconds = 86400; + create_operation.preimage_hash = hash_it( pre_image ); + create_operation.preimage_size = preimage_size; + create_operation.from = alice_id; + create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation ); + trx.operations.push_back( create_operation ); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + graphene::chain::signed_block blk = generate_block(); + processed_transaction alice_trx = blk.transactions[0]; + alice_htlc_id = alice_trx.operation_results[0].get(); + } + + // make sure Alice's money gets put on hold (100 - 20 - 4(fee) ) + BOOST_CHECK_EQUAL( get_balance( alice_id, graphene::chain::asset_id_type()), 76 * GRAPHENE_BLOCKCHAIN_PRECISION ); + + // extend the timeout so that Bob has more time + { + graphene::chain::htlc_extend_operation extend_operation; + extend_operation.htlc_id = alice_htlc_id; + extend_operation.seconds_to_add = 86400; + extend_operation.update_issuer = alice_id; + extend_operation.fee = db.current_fee_schedule().calculate_fee( extend_operation ); + trx.operations.push_back( extend_operation ); + sign( trx, alice_private_key ); + PUSH_TX( db, trx, ~0 ); + trx.clear(); + generate_blocks( db.head_block_time() + fc::seconds(87000) ); + set_expiration( db, trx ); + } + + // make sure Alice's money is still on hold, and account for extra fee + BOOST_CHECK_EQUAL( get_balance( alice_id, graphene::chain::asset_id_type()), 72 * GRAPHENE_BLOCKCHAIN_PRECISION ); + + // send a redeem operation to claim the funds + { + graphene::chain::htlc_redeem_operation update_operation; + update_operation.redeemer = bob_id; + update_operation.htlc_id = alice_htlc_id; + update_operation.preimage = pre_image; + update_operation.fee = db.current_fee_schedule().calculate_fee( update_operation ); + trx.operations.push_back( update_operation ); + sign(trx, bob_private_key); + PUSH_TX( db, trx, ~0 ); + generate_block(); + trx.clear(); + } + // verify funds end up in Bob's account (100 + 20 - 4(fee) ) + BOOST_CHECK_EQUAL( get_balance(bob_id, graphene::chain::asset_id_type()), 116 * GRAPHENE_BLOCKCHAIN_PRECISION ); + // verify funds remain out of Alice's acount ( 100 - 20 - 4 ) + BOOST_CHECK_EQUAL( get_balance(alice_id, graphene::chain::asset_id_type()), 72 * GRAPHENE_BLOCKCHAIN_PRECISION ); +} FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( other_peoples_money ) +{ +try { + advance_past_hardfork(this); + + ACTORS((alice)(bob)); + + int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION ); + + transfer( committee_account, alice_id, graphene::chain::asset(init_balance) ); + + uint16_t preimage_size = 256; + std::vector pre_image(256); + generate_random_preimage(preimage_size, pre_image); + + graphene::chain::htlc_id_type alice_htlc_id; + // cler everything out + generate_block(); + trx.clear(); + // Bob attempts to put a contract on the blockchain using Alice's funds + { + graphene::chain::htlc_create_operation create_operation; + create_operation.amount = graphene::chain::asset( 1 * GRAPHENE_BLOCKCHAIN_PRECISION ); + create_operation.to = bob_id; + create_operation.claim_period_seconds = 3; + create_operation.preimage_hash = hash_it( pre_image ); + create_operation.preimage_size = preimage_size; + create_operation.from = alice_id; + create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation ); + trx.operations.push_back(create_operation); + sign(trx, bob_private_key); + GRAPHENE_CHECK_THROW( PUSH_TX( db, trx ), fc::exception); + trx.clear(); + } + // now try the same but with Alice's signature (should work) + { + graphene::chain::htlc_create_operation create_operation; + create_operation.amount = graphene::chain::asset( 1 * GRAPHENE_BLOCKCHAIN_PRECISION ); + create_operation.to = bob_id; + create_operation.claim_period_seconds = 3; + create_operation.preimage_hash = hash_it( pre_image ); + create_operation.preimage_size = preimage_size; + create_operation.from = alice_id; + create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation ); + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + PUSH_TX( db, trx ); + trx.clear(); + } +} FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( htlc_hardfork_test ) +{ + try { + { + // try to set committee parameters before hardfork + proposal_create_operation cop = proposal_create_operation::committee_proposal( + db.get_global_properties().parameters, db.head_block_time()); + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10; + committee_member_update_global_parameters_operation cmuop; + graphene::chain::htlc_options new_params; + new_params.max_preimage_size = 2048; + new_params.max_timeout_secs = 60 * 60 * 24 * 28; + cmuop.new_parameters.extensions.value.updatable_htlc_options = new_params; + cop.proposed_ops.emplace_back(cmuop); + trx.operations.push_back(cop); + // update with signatures + proposal_update_operation uop; + uop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + uop.active_approvals_to_add = {get_account("init0").get_id(), get_account("init1").get_id(), + get_account("init2").get_id(), get_account("init3").get_id(), + get_account("init4").get_id(), get_account("init5").get_id(), + get_account("init6").get_id(), get_account("init7").get_id()}; + trx.operations.push_back(uop); + sign( trx, init_account_priv_key ); + BOOST_TEST_MESSAGE("Sending proposal."); + GRAPHENE_CHECK_THROW(db.push_transaction(trx), fc::exception); + BOOST_TEST_MESSAGE("Verifying that proposal did not succeeed."); + BOOST_CHECK(!db.get_global_properties().parameters.extensions.value.updatable_htlc_options.valid()); + trx.clear(); + } + + { + BOOST_TEST_MESSAGE("Attempting to set HTLC fees before hard fork."); + + // get existing fee_schedule + const chain_parameters& existing_params = db.get_global_properties().parameters; + const fee_schedule_type& existing_fee_schedule = *(existing_params.current_fees); + // create a new fee_shedule + fee_schedule_type new_fee_schedule; + new_fee_schedule.scale = existing_fee_schedule.scale; + // replace the old with the new + flat_map params_map = get_htlc_fee_parameters(); + for(auto param : existing_fee_schedule.parameters) + { + auto itr = params_map.find(param.which()); + if (itr == params_map.end()) + new_fee_schedule.parameters.insert(param); + else + { + new_fee_schedule.parameters.insert( (*itr).second); + } + } + proposal_create_operation cop = proposal_create_operation::committee_proposal( + db.get_global_properties().parameters, db.head_block_time()); + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10; + committee_member_update_global_parameters_operation uop; + uop.new_parameters.current_fees = new_fee_schedule; + cop.proposed_ops.emplace_back(uop); + cop.fee = asset( 100000 ); + trx.operations.push_back( cop ); + GRAPHENE_CHECK_THROW(db.push_transaction( trx ), fc::exception); + trx.clear(); + } + + // now things should start working... + BOOST_TEST_MESSAGE("Advancing to HTLC hardfork time."); + advance_past_hardfork(this); + + proposal_id_type good_proposal_id; + BOOST_TEST_MESSAGE( "Creating a proposal to change the max_preimage_size to 2048 and set higher fees" ); + { + // get existing fee_schedule + const chain_parameters& existing_params = db.get_global_properties().parameters; + const fee_schedule_type& existing_fee_schedule = *(existing_params.current_fees); + // create a new fee_shedule + fee_schedule_type new_fee_schedule; + new_fee_schedule.scale = existing_fee_schedule.scale; + // replace the old with the new + flat_map params_map = get_htlc_fee_parameters(); + for(auto param : existing_fee_schedule.parameters) + { + auto itr = params_map.find(param.which()); + if (itr == params_map.end()) + new_fee_schedule.parameters.insert(param); + else + { + new_fee_schedule.parameters.insert( (*itr).second); + } + } + proposal_create_operation cop = proposal_create_operation::committee_proposal(db.get_global_properties().parameters, db.head_block_time()); + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10; + committee_member_update_global_parameters_operation uop; + graphene::chain::htlc_options new_params; + new_params.max_preimage_size = 2048; + new_params.max_timeout_secs = 60 * 60 * 24 * 28; + uop.new_parameters.extensions.value.updatable_htlc_options = new_params; + uop.new_parameters.current_fees = new_fee_schedule; + cop.proposed_ops.emplace_back(uop); + trx.operations.push_back(cop); + graphene::chain::processed_transaction proc_trx =db.push_transaction(trx); + good_proposal_id = proc_trx.operation_results[0].get(); + } + + BOOST_TEST_MESSAGE( "Updating proposal by signing with the committee_member private key" ); + { + proposal_update_operation uop; + uop.proposal = good_proposal_id; + uop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + uop.active_approvals_to_add = {get_account("init0").get_id(), get_account("init1").get_id(), + get_account("init2").get_id(), get_account("init3").get_id(), + get_account("init4").get_id(), get_account("init5").get_id(), + get_account("init6").get_id(), get_account("init7").get_id()}; + trx.operations.push_back(uop); + sign( trx, init_account_priv_key ); + db.push_transaction(trx); + BOOST_CHECK(good_proposal_id(db).is_authorized_to_execute(db)); + } + BOOST_TEST_MESSAGE( "Verifying that the parameters didn't change immediately" ); + + BOOST_CHECK_EQUAL(db.get_global_properties().parameters.extensions.value.updatable_htlc_options->max_preimage_size, 19200u); + + BOOST_TEST_MESSAGE( "Generating blocks until proposal expires" ); + generate_blocks(good_proposal_id(db).expiration_time + 5); + BOOST_TEST_MESSAGE( "Verify that the parameters still have not changed" ); + BOOST_CHECK_EQUAL(db.get_global_properties().parameters.extensions.value.updatable_htlc_options->max_preimage_size, 19200u); + + BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + + BOOST_TEST_MESSAGE( "Verify that the change has been implemented" ); + BOOST_CHECK_EQUAL(db.get_global_properties().parameters.extensions.value.updatable_htlc_options->max_preimage_size, 2048u); + BOOST_CHECK_EQUAL(db.get_global_properties().parameters.current_fees->get().fee, 2 * GRAPHENE_BLOCKCHAIN_PRECISION); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( htlc_before_hardfork ) +{ try { + ACTORS((alice)(bob)); + + int64_t init_balance(100000); + + transfer( committee_account, alice_id, graphene::chain::asset(init_balance) ); + + uint16_t preimage_size = 256; + std::vector pre_image(256); + generate_random_preimage(preimage_size, pre_image); + + graphene::chain::htlc_id_type alice_htlc_id; + // clear everything out + generate_block(); + trx.clear(); + + // Alice tries to put a contract on the blockchain + { + graphene::chain::htlc_create_operation create_operation; + + create_operation.amount = graphene::chain::asset( 10000 ); + create_operation.to = bob_id; + create_operation.claim_period_seconds = 60; + create_operation.preimage_hash = hash_it( pre_image ); + create_operation.preimage_size = preimage_size; + create_operation.from = alice_id; + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + GRAPHENE_CHECK_THROW(PUSH_TX(db, trx, ~0), fc::exception); + trx.clear(); + } + + // Propose htlc_create + { + proposal_create_operation pco; + pco.expiration_time = db.head_block_time() + fc::minutes(1); + pco.fee_paying_account = alice_id; + + graphene::chain::htlc_create_operation create_operation; + create_operation.amount = graphene::chain::asset( 10000 ); + create_operation.to = bob_id; + create_operation.claim_period_seconds = 60; + create_operation.preimage_hash = hash_it( pre_image ); + create_operation.preimage_size = preimage_size; + create_operation.from = alice_id; + + pco.proposed_ops.emplace_back( create_operation ); + trx.operations.push_back( pco ); + GRAPHENE_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::assert_exception ); + trx.clear(); + } + + // Propose htlc_redeem + { + proposal_create_operation pco; + pco.expiration_time = db.head_block_time() + fc::minutes(1); + pco.fee_paying_account = alice_id; + + graphene::chain::htlc_redeem_operation rop; + rop.redeemer = bob_id; + rop.htlc_id = alice_htlc_id; + string preimage_str = "Arglebargle"; + rop.preimage.insert( rop.preimage.begin(), preimage_str.begin(), preimage_str.end() ); + + pco.proposed_ops.emplace_back( rop ); + trx.operations.push_back( pco ); + GRAPHENE_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::assert_exception ); + trx.clear(); + } + + // Propose htlc_extend + { + proposal_create_operation pco; + pco.expiration_time = db.head_block_time() + fc::minutes(1); + pco.fee_paying_account = alice_id; + + graphene::chain::htlc_extend_operation xop; + xop.htlc_id = alice_htlc_id; + xop.seconds_to_add = 100; + xop.update_issuer = alice_id; + + pco.proposed_ops.emplace_back( xop ); + trx.operations.push_back( pco ); + GRAPHENE_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::assert_exception ); + trx.clear(); + } +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( fee_calculations ) +{ + // create + { + htlc_create_operation::fee_parameters_type create_fee; + create_fee.fee = 2; + create_fee.fee_per_day = 2; + htlc_create_operation create; + // no days + create.claim_period_seconds = 0; + BOOST_CHECK_EQUAL( create.calculate_fee(create_fee).value, 2 ); + // exactly 1 day + create.claim_period_seconds = 60 * 60 * 24; + BOOST_CHECK_EQUAL( create.calculate_fee(create_fee).value, 4 ); + // tad over a day + create.claim_period_seconds++; + BOOST_CHECK_EQUAL( create.calculate_fee(create_fee).value, 6 ); + } + // redeem + { + htlc_redeem_operation::fee_parameters_type redeem_fee; + redeem_fee.fee_per_kb = 2; + redeem_fee.fee = 2; + htlc_redeem_operation redeem; + // no preimage + redeem.preimage = std::vector(); + BOOST_CHECK_EQUAL( redeem.calculate_fee( redeem_fee ).value, 2 ) ; + // exactly 1KB + std::string test(1024, 'a'); + redeem.preimage = std::vector( test.begin(), test.end() ); + BOOST_CHECK_EQUAL( redeem.calculate_fee( redeem_fee ).value, 4 ) ; + // just 1 byte over 1KB + std::string larger(1025, 'a'); + redeem.preimage = std::vector( larger.begin(), larger.end() ); + BOOST_CHECK_EQUAL( redeem.calculate_fee( redeem_fee ).value, 6 ) ; + } + // extend + { + htlc_extend_operation::fee_parameters_type extend_fee; + extend_fee.fee = 2; + extend_fee.fee_per_day = 2; + htlc_extend_operation extend; + // no days + extend.seconds_to_add = 0; + BOOST_CHECK_EQUAL( extend.calculate_fee( extend_fee ).value, 2 ); + // exactly 1 day + extend.seconds_to_add = 60 * 60 * 24; + BOOST_CHECK_EQUAL( extend.calculate_fee( extend_fee ).value, 4 ); + // 1 day and 1 second + extend.seconds_to_add++; + BOOST_CHECK_EQUAL( extend.calculate_fee( extend_fee ).value, 6 ); + } +} + +BOOST_AUTO_TEST_CASE( htlc_blacklist ) +{ +try { + ACTORS((nathan)(alice)(bob)); + + upgrade_to_lifetime_member( nathan ); + + // create a UIA + const asset_id_type uia_id = create_user_issued_asset( "NATHANCOIN", nathan, white_list ).id; + // Make a whitelist authority + { + BOOST_TEST_MESSAGE( "Changing the whitelist authority" ); + asset_update_operation uop; + uop.issuer = nathan_id; + uop.asset_to_update = uia_id; + uop.new_options = uia_id(db).options; + uop.new_options.blacklist_authorities.insert(nathan_id); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + + + int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION); + fund( alice, graphene::chain::asset(init_balance) ); + fund( bob, graphene::chain::asset(init_balance) ); + + advance_past_hardfork(this); + + // blacklist bob + { + graphene::chain::account_whitelist_operation op; + op.authorizing_account = nathan_id; + op.account_to_list = bob_id; + op.new_listing = graphene::chain::account_whitelist_operation::account_listing::black_listed; + op.fee = db.current_fee_schedule().calculate_fee( op ); + trx.operations.push_back( op ); + sign( trx, nathan_private_key ); + PUSH_TX( db, trx, ~0 ); + trx.clear(); + generate_block(); + } + + issue_uia( alice_id, asset( init_balance, uia_id ) ); + + uint16_t preimage_size = 256; + std::vector pre_image(preimage_size); + generate_random_preimage(preimage_size, pre_image); + + // Alice attempts to put a contract on the blockchain + { + graphene::chain::htlc_create_operation create_operation; + + create_operation.amount = graphene::chain::asset( 20 * GRAPHENE_BLOCKCHAIN_PRECISION, uia_id ); + create_operation.to = bob_id; + create_operation.claim_period_seconds = 86400; + create_operation.preimage_hash = hash_it( pre_image ); + create_operation.preimage_size = preimage_size; + create_operation.from = alice_id; + create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation ); + trx.operations.push_back( create_operation ); + sign(trx, alice_private_key); + // bob cannot accept it, so it fails + GRAPHENE_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + trx.clear(); + } + + // unblacklist Bob + { + graphene::chain::account_whitelist_operation op; + op.authorizing_account = nathan_id; + op.account_to_list = bob_id; + op.new_listing = graphene::chain::account_whitelist_operation::account_listing::no_listing; + op.fee = db.current_fee_schedule().calculate_fee( op ); + trx.operations.push_back( op ); + sign( trx, nathan_private_key ); + PUSH_TX( db, trx, ~0 ); + trx.clear(); + generate_block(); + } + + graphene::chain::htlc_id_type alice_htlc_id; + + // Alice again attempts to put a contract on the blockchain + { + graphene::chain::htlc_create_operation create_operation; + + create_operation.amount = graphene::chain::asset( 20 * GRAPHENE_BLOCKCHAIN_PRECISION, uia_id ); + create_operation.to = bob_id; + create_operation.claim_period_seconds = 86400; + create_operation.preimage_hash = hash_it( pre_image ); + create_operation.preimage_size = preimage_size; + create_operation.from = alice_id; + create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation ); + trx.operations.push_back( create_operation ); + sign(trx, alice_private_key); + // bob can now accept it, so it works + PUSH_TX( db, trx, ~0 ); + trx.clear(); + graphene::chain::signed_block blk = generate_block(); + processed_transaction alice_trx = blk.transactions[0]; + alice_htlc_id = alice_trx.operation_results[0].get(); + } + + // blacklist bob + { + graphene::chain::account_whitelist_operation op; + op.authorizing_account = nathan_id; + op.account_to_list = bob_id; + op.new_listing = graphene::chain::account_whitelist_operation::account_listing::black_listed; + op.fee = db.current_fee_schedule().calculate_fee( op ); + trx.operations.push_back( op ); + sign( trx, nathan_private_key ); + PUSH_TX( db, trx, ~0 ); + trx.clear(); + generate_block(); + } + + // bob can redeem even though he's blacklisted + { + graphene::chain::htlc_redeem_operation update_operation; + update_operation.redeemer = bob_id; + update_operation.htlc_id = alice_htlc_id; + update_operation.preimage = pre_image; + update_operation.fee = db.current_fee_schedule().calculate_fee( update_operation ); + trx.operations.push_back( update_operation ); + sign(trx, bob_private_key); + PUSH_TX( db, trx, ~0 ); + generate_block(); + trx.clear(); + } + +} FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/market_fee_sharing_tests.cpp b/tests/tests/market_fee_sharing_tests.cpp new file mode 100644 index 0000000000..c8df1f56ac --- /dev/null +++ b/tests/tests/market_fee_sharing_tests.cpp @@ -0,0 +1,975 @@ +#include + +#include +#include +#include +#include +#include + + +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; + +namespace fc +{ + template + std::basic_ostream& operator<<(std::basic_ostream& os, safe const& sf) + { + os << sf.value; + return os; + } +} + +struct reward_database_fixture : database_fixture +{ + using whitelist_market_fee_sharing_t = fc::optional>; + + reward_database_fixture() + : database_fixture(HARDFORK_1268_TIME - 100) + { + } + + void update_asset( const account_id_type& issuer_id, + const fc::ecc::private_key& private_key, + const asset_id_type& asset_id, + uint16_t reward_percent, + const whitelist_market_fee_sharing_t &whitelist_market_fee_sharing = whitelist_market_fee_sharing_t{}, + const flat_set &blacklist = flat_set()) + { + asset_update_operation op; + op.issuer = issuer_id; + op.asset_to_update = asset_id; + op.new_options = asset_id(db).options; + op.new_options.extensions.value.reward_percent = reward_percent; + op.new_options.extensions.value.whitelist_market_fee_sharing = whitelist_market_fee_sharing; + op.new_options.blacklist_authorities = blacklist; + + signed_transaction tx; + tx.operations.push_back( op ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, private_key ); + PUSH_TX( db, tx ); + } + + void asset_update_blacklist_authority(const account_id_type& issuer_id, + const asset_id_type& asset_id, + const account_id_type& authority_account_id, + const fc::ecc::private_key& issuer_private_key) + { + asset_update_operation uop; + uop.issuer = issuer_id; + uop.asset_to_update = asset_id; + uop.new_options = asset_id(db).options; + uop.new_options.blacklist_authorities.insert(authority_account_id); + + signed_transaction tx; + tx.operations.push_back( uop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, issuer_private_key ); + PUSH_TX( db, tx ); + } + + void add_account_to_blacklist(const account_id_type& authorizing_account_id, + const account_id_type& blacklisted_account_id, + const fc::ecc::private_key& authorizing_account_private_key) + { + account_whitelist_operation wop; + wop.authorizing_account = authorizing_account_id; + wop.account_to_list = blacklisted_account_id; + wop.new_listing = account_whitelist_operation::black_listed; + + signed_transaction tx; + tx.operations.push_back( wop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, authorizing_account_private_key ); + PUSH_TX( db, tx); + } + + void generate_blocks_past_hf1268() + { + database_fixture::generate_blocks( HARDFORK_1268_TIME ); + database_fixture::generate_block(); + } + + asset core_asset(int64_t x ) + { + return asset( x*core_precision ); + }; + + const share_type core_precision = asset::scaled_precision( asset_id_type()(db).precision ); + + void create_vesting_balance_object(const account_id_type& account_id, vesting_balance_type balance_type ) + { + auto block_time = db.head_block_time(); + + db.create([&account_id, &block_time, balance_type] (vesting_balance_object &vbo) { + vbo.owner = account_id; + vbo.balance_type = balance_type; + }); + }; +}; + +BOOST_FIXTURE_TEST_SUITE( fee_sharing_tests, reward_database_fixture ) + +BOOST_AUTO_TEST_CASE(cannot_create_asset_with_additional_options_before_hf) +{ + try + { + ACTOR(issuer); + + price price(asset(1, asset_id_type(1)), asset(1)); + uint16_t market_fee_percent = 100; + + additional_asset_options_t options; + options.value.reward_percent = 100; + options.value.whitelist_market_fee_sharing = flat_set{issuer_id}; + + GRAPHENE_CHECK_THROW(create_user_issued_asset("USD", + issuer, + charge_market_fee, + price, + 2, + market_fee_percent, + options), + fc::assert_exception); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(create_asset_with_additional_options_after_hf) +{ + try + { + ACTOR(issuer); + + generate_blocks_past_hf1268(); + + uint16_t reward_percent = 100; + flat_set whitelist = {issuer_id}; + price price(asset(1, asset_id_type(1)), asset(1)); + uint16_t market_fee_percent = 100; + + additional_asset_options_t options; + options.value.reward_percent = reward_percent; + options.value.whitelist_market_fee_sharing = whitelist; + + asset_object usd_asset = create_user_issued_asset("USD", + issuer, + charge_market_fee, + price, + 2, + market_fee_percent, + options); + + additional_asset_options usd_options = usd_asset.options.extensions.value; + BOOST_CHECK_EQUAL(reward_percent, *usd_options.reward_percent); + BOOST_CHECK(whitelist == *usd_options.whitelist_market_fee_sharing); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(cannot_update_additional_options_before_hf) +{ + try + { + ACTOR(issuer); + + asset_object usd_asset = create_user_issued_asset("USD", issuer, charge_market_fee); + + flat_set whitelist = {issuer_id}; + GRAPHENE_CHECK_THROW( + update_asset(issuer_id, issuer_private_key, usd_asset.get_id(), 40, whitelist), + fc::assert_exception ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(update_additional_options_after_hf) +{ + try + { + ACTOR(issuer); + + asset_object usd_asset = create_user_issued_asset("USD", issuer, charge_market_fee); + + generate_blocks_past_hf1268(); + + uint16_t reward_percent = 40; + flat_set whitelist = {issuer_id}; + update_asset(issuer_id, issuer_private_key, usd_asset.get_id(), reward_percent, whitelist); + + asset_object updated_asset = usd_asset.get_id()(db); + additional_asset_options options = updated_asset.options.extensions.value; + BOOST_CHECK_EQUAL(reward_percent, *options.reward_percent); + BOOST_CHECK(whitelist == *options.whitelist_market_fee_sharing); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(asset_rewards_test) +{ + try + { + ACTORS((registrar)(alicereferrer)(bobreferrer)(izzy)(jill)); + + auto register_account = [&](const string& name, const account_object& referrer) -> const account_object& + { + uint16_t referrer_percent = GRAPHENE_1_PERCENT; + fc::ecc::private_key _private_key = generate_private_key(name); + public_key_type _public_key = _private_key.get_public_key(); + return create_account(name, registrar, referrer, referrer_percent, _public_key); + }; + + // Izzy issues asset to Alice + // Jill issues asset to Bob + // Alice and Bob trade in the market and pay fees + // Bob's and Alice's referrers can get reward + upgrade_to_lifetime_member(registrar); + upgrade_to_lifetime_member(alicereferrer); + upgrade_to_lifetime_member(bobreferrer); + + auto alice = register_account("alice", alicereferrer); + auto bob = register_account("bob", bobreferrer); + + transfer( committee_account, alice.id, core_asset(1000000) ); + transfer( committee_account, bob.id, core_asset(1000000) ); + transfer( committee_account, izzy_id, core_asset(1000000) ); + transfer( committee_account, jill_id, core_asset(1000000) ); + + constexpr auto izzycoin_reward_percent = 10*GRAPHENE_1_PERCENT; + constexpr auto jillcoin_reward_percent = 20*GRAPHENE_1_PERCENT; + + constexpr auto izzycoin_market_percent = 10*GRAPHENE_1_PERCENT; + constexpr auto jillcoin_market_percent = 20*GRAPHENE_1_PERCENT; + + asset_id_type izzycoin_id = create_bitasset( "IZZYCOIN", izzy_id, izzycoin_market_percent ).id; + asset_id_type jillcoin_id = create_bitasset( "JILLCOIN", jill_id, jillcoin_market_percent ).id; + + generate_blocks_past_hf1268(); + + update_asset(izzy_id, izzy_private_key, izzycoin_id, izzycoin_reward_percent); + update_asset(jill_id, jill_private_key, jillcoin_id, jillcoin_reward_percent); + + const share_type izzy_prec = asset::scaled_precision( asset_id_type(izzycoin_id)(db).precision ); + const share_type jill_prec = asset::scaled_precision( asset_id_type(jillcoin_id)(db).precision ); + + auto _izzy = [&]( int64_t x ) -> asset + { return asset( x*izzy_prec, izzycoin_id ); }; + auto _jill = [&]( int64_t x ) -> asset + { return asset( x*jill_prec, jillcoin_id ); }; + + update_feed_producers( izzycoin_id(db), { izzy_id } ); + update_feed_producers( jillcoin_id(db), { jill_id } ); + + // Izzycoin is worth 100 BTS + price_feed feed; + feed.settlement_price = price( _izzy(1), core_asset(100) ); + feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + feed.maximum_short_squeeze_ratio = 150 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + publish_feed( izzycoin_id(db), izzy, feed ); + + // Jillcoin is worth 30 BTS + feed.settlement_price = price( _jill(1), core_asset(30) ); + feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + feed.maximum_short_squeeze_ratio = 150 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + publish_feed( jillcoin_id(db), jill, feed ); + + enable_fees(); + + // Alice and Bob create some coins + borrow( alice.id, _izzy( 1500), core_asset( 600000) ); + borrow( bob.id, _jill(2000), core_asset(180000) ); + + // Alice and Bob place orders which match + create_sell_order( alice.id, _izzy(1000), _jill(1500) ); // Alice is willing to sell her 1000 Izzy's for 1.5 Jill + create_sell_order( bob.id, _jill(1500), _izzy(1000) ); // Bob is buying up to 1500 Izzy's for up to 0.6 Jill + + // 1000 Izzys and 1500 Jills are matched, so the fees should be + // 100 Izzy (10%) and 300 Jill (20%). + // Bob's and Alice's referrers should get rewards + share_type bob_refereer_reward = get_market_fee_reward( bob.referrer, izzycoin_id ); + share_type alice_refereer_reward = get_market_fee_reward( alice.referrer, jillcoin_id ); + + // Bob's and Alice's registrars should get rewards + share_type bob_registrar_reward = get_market_fee_reward( bob.registrar, izzycoin_id ); + share_type alice_registrar_reward = get_market_fee_reward( alice.registrar, jillcoin_id ); + + auto calculate_percent = [](const share_type& value, uint16_t percent) + { + auto a(value.value); + a *= percent; + a /= GRAPHENE_100_PERCENT; + return a; + }; + + BOOST_CHECK_GT( bob_refereer_reward, 0 ); + BOOST_CHECK_GT( alice_refereer_reward, 0 ); + BOOST_CHECK_GT( bob_registrar_reward, 0 ); + BOOST_CHECK_GT( alice_registrar_reward, 0 ); + + const auto izzycoin_market_fee = calculate_percent(_izzy(1000).amount, izzycoin_market_percent); + const auto izzycoin_reward = calculate_percent(izzycoin_market_fee, izzycoin_reward_percent); + BOOST_CHECK_EQUAL( izzycoin_reward, bob_refereer_reward + bob_registrar_reward ); + BOOST_CHECK_EQUAL( calculate_percent(izzycoin_reward, bob.referrer_rewards_percentage), bob_refereer_reward ); + + const auto jillcoin_market_fee = calculate_percent(_jill(1500).amount, jillcoin_market_percent); + const auto jillcoin_reward = calculate_percent(jillcoin_market_fee, jillcoin_reward_percent); + BOOST_CHECK_EQUAL( jillcoin_reward, alice_refereer_reward + alice_registrar_reward ); + BOOST_CHECK_EQUAL( calculate_percent(jillcoin_reward, alice.referrer_rewards_percentage), alice_refereer_reward ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(asset_claim_reward_test) +{ + try + { + ACTORS((jill)(izzy)); + constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; + + upgrade_to_lifetime_member(izzy); + + price price(asset(1, asset_id_type(1)), asset(1)); + uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT; + const asset_object jillcoin = create_user_issued_asset( "JCOIN", jill, charge_market_fee, price, 2, market_fee_percent ); + + const account_object alice = create_account("alice", izzy, izzy, 50/*0.5%*/); + const account_object bob = create_account("bob", izzy, izzy, 50/*0.5%*/); + + // prepare users' balance + issue_uia( alice, jillcoin.amount( 20000000 ) ); + + transfer( committee_account, alice.get_id(), core_asset(1000) ); + transfer( committee_account, bob.get_id(), core_asset(1000) ); + transfer( committee_account, izzy.get_id(), core_asset(1000) ); + + generate_blocks_past_hf1268(); + // update_asset: set referrer percent + update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent); + + // Alice and Bob place orders which match + create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); + create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); + + const int64_t izzy_reward = get_market_fee_reward( izzy, jillcoin ); + const int64_t izzy_balance = get_balance( izzy, jillcoin ); + + BOOST_CHECK_GT(izzy_reward, 0); + + auto claim_reward = [&]( account_object referrer, asset amount_to_claim, fc::ecc::private_key private_key ) + { + vesting_balance_withdraw_operation op; + op.vesting_balance = vesting_balance_id_type(0); + op.owner = referrer.get_id(); + op.amount = amount_to_claim; + + signed_transaction tx; + tx.operations.push_back( op ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, private_key ); + PUSH_TX( db, tx ); + }; + + const int64_t amount_to_claim = 3; + claim_reward( izzy, jillcoin.amount(amount_to_claim), izzy_private_key ); + + BOOST_CHECK_EQUAL(get_balance( izzy, jillcoin ), izzy_balance + amount_to_claim); + BOOST_CHECK_EQUAL(get_market_fee_reward( izzy, jillcoin ), izzy_reward - amount_to_claim); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(create_actors) +{ + try + { + ACTORS((jill)(izzyregistrar)(izzyreferrer)); + + upgrade_to_lifetime_member(izzyregistrar); + upgrade_to_lifetime_member(izzyreferrer); + + price price(asset(1, asset_id_type(1)), asset(1)); + uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT; + auto obj = jill_id(db); + const asset_object jillcoin = create_user_issued_asset( "JCOIN", jill, charge_market_fee, price, 2, market_fee_percent ); + + const account_object alice = create_account("alice", izzyregistrar, izzyreferrer, 50/*0.5%*/); + const account_object bob = create_account("bob", izzyregistrar, izzyreferrer, 50/*0.5%*/); + + // prepare users' balance + issue_uia( alice, jillcoin.amount( 20000000 ) ); + + transfer( committee_account, alice.get_id(), core_asset(1000) ); + transfer( committee_account, bob.get_id(), core_asset(1000) ); + transfer( committee_account, izzyregistrar.get_id(), core_asset(1000) ); + transfer( committee_account, izzyreferrer.get_id(), core_asset(1000) ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(white_list_is_empty_test) +{ + try + { + INVOKE(create_actors); + + generate_blocks_past_hf1268(); + GET_ACTOR(jill); + + constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; + const asset_object &jillcoin = get_asset("JCOIN"); + + flat_set whitelist; + update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist); + + GET_ACTOR(izzyregistrar); + GET_ACTOR(izzyreferrer); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 ); + + GET_ACTOR(alice); + GET_ACTOR(bob); + // Alice and Bob place orders which match + create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); + create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); + + const auto izzyregistrar_reward = get_market_fee_reward( izzyregistrar, jillcoin ); + const auto izzyreferrer_reward = get_market_fee_reward( izzyreferrer, jillcoin ); + BOOST_CHECK_GT(izzyregistrar_reward , 0); + BOOST_CHECK_GT(izzyreferrer_reward , 0); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(white_list_contains_registrar_test) +{ + try + { + INVOKE(create_actors); + + generate_blocks_past_hf1268(); + GET_ACTOR(jill); + + constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; + const asset_object &jillcoin = get_asset("JCOIN"); + + GET_ACTOR(izzyregistrar); + GET_ACTOR(izzyreferrer); + flat_set whitelist = {jill_id, izzyregistrar_id}; + + update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist); + + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 ); + + GET_ACTOR(alice); + GET_ACTOR(bob); + // Alice and Bob place orders which match + create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); + create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); + + const auto izzyregistrar_reward = get_market_fee_reward( izzyregistrar, jillcoin ); + const auto izzyreferrer_reward = get_market_fee_reward( izzyreferrer, jillcoin ); + BOOST_CHECK_GT(izzyregistrar_reward , 0); + BOOST_CHECK_GT(izzyreferrer_reward , 0); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(white_list_contains_referrer_test) +{ + try + { + INVOKE(create_actors); + + generate_blocks_past_hf1268(); + GET_ACTOR(jill); + + constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; + const asset_object &jillcoin = get_asset("JCOIN"); + + GET_ACTOR(izzyregistrar); + GET_ACTOR(izzyreferrer); + flat_set whitelist = {jill_id, izzyreferrer_id}; + + update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist); + + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 ); + + GET_ACTOR(alice); + GET_ACTOR(bob); + // Alice and Bob place orders which match + create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); + create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); + + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(white_list_doesnt_contain_registrar_test) +{ + try + { + INVOKE(create_actors); + + generate_blocks_past_hf1268(); + GET_ACTOR(jill); + + constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; + const asset_object &jillcoin = get_asset("JCOIN"); + + GET_ACTOR(alice); + flat_set whitelist = {jill_id, alice_id}; + + update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist); + + GET_ACTOR(izzyregistrar); + GET_ACTOR(izzyreferrer); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 ); + + GET_ACTOR(bob); + // Alice and Bob place orders which match + create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); + create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); + + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(create_asset_via_proposal_test) +{ + try + { + ACTOR(issuer); + price core_exchange_rate(asset(1, asset_id_type(1)), asset(1)); + + asset_create_operation create_op; + create_op.issuer = issuer.id; + create_op.fee = asset(); + create_op.symbol = "ASSET"; + create_op.common_options.max_supply = 0; + create_op.precision = 2; + create_op.common_options.core_exchange_rate = core_exchange_rate; + create_op.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + create_op.common_options.flags = charge_market_fee; + + additional_asset_options_t options; + options.value.reward_percent = 100; + options.value.whitelist_market_fee_sharing = flat_set{issuer_id}; + create_op.common_options.extensions = std::move(options);; + + const auto& curfees = *db.get_global_properties().parameters.current_fees; + const auto& proposal_create_fees = curfees.get(); + proposal_create_operation prop; + prop.fee_paying_account = issuer_id; + prop.proposed_ops.emplace_back( create_op ); + prop.expiration_time = db.head_block_time() + fc::days(1); + prop.fee = asset( proposal_create_fees.fee + proposal_create_fees.price_per_kbyte ); + + { + signed_transaction tx; + tx.operations.push_back( prop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, issuer_private_key ); + GRAPHENE_CHECK_THROW(PUSH_TX( db, tx ), fc::exception); + } + + generate_blocks_past_hf1268(); + + { + prop.expiration_time = db.head_block_time() + fc::days(1); + signed_transaction tx; + tx.operations.push_back( prop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, issuer_private_key ); + PUSH_TX( db, tx ); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(update_asset_via_proposal_test) +{ + try + { + ACTOR(issuer); + asset_object usd_asset = create_user_issued_asset("USD", issuer, charge_market_fee); + + additional_asset_options_t options; + options.value.reward_percent = 100; + options.value.whitelist_market_fee_sharing = flat_set{issuer_id}; + + asset_update_operation update_op; + update_op.issuer = issuer_id; + update_op.asset_to_update = usd_asset.get_id(); + asset_options new_options; + update_op.new_options = usd_asset.options; + update_op.new_options.extensions = std::move(options); + + const auto& curfees = *db.get_global_properties().parameters.current_fees; + const auto& proposal_create_fees = curfees.get(); + proposal_create_operation prop; + prop.fee_paying_account = issuer_id; + prop.proposed_ops.emplace_back( update_op ); + prop.expiration_time = db.head_block_time() + fc::days(1); + prop.fee = asset( proposal_create_fees.fee + proposal_create_fees.price_per_kbyte ); + + { + signed_transaction tx; + tx.operations.push_back( prop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, issuer_private_key ); + GRAPHENE_CHECK_THROW(PUSH_TX( db, tx ), fc::exception); + } + + generate_blocks_past_hf1268(); + + { + prop.expiration_time = db.head_block_time() + fc::days(1); + signed_transaction tx; + tx.operations.push_back( prop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, issuer_private_key ); + PUSH_TX( db, tx ); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(issue_asset){ + try + { + ACTORS((alice)(bob)(izzy)(jill)); + // Izzy issues asset to Alice (Izzycoin market percent - 10%) + // Jill issues asset to Bob (Jillcoin market percent - 20%) + + fund( alice, core_asset(1000000) ); + fund( bob, core_asset(1000000) ); + fund( izzy, core_asset(1000000) ); + fund( jill, core_asset(1000000) ); + + price price(asset(1, asset_id_type(1)), asset(1)); + constexpr auto izzycoin_market_percent = 10*GRAPHENE_1_PERCENT; + asset_object izzycoin = create_user_issued_asset( "IZZYCOIN", izzy, charge_market_fee, price, 2, izzycoin_market_percent ); + + constexpr auto jillcoin_market_percent = 20*GRAPHENE_1_PERCENT; + asset_object jillcoin = create_user_issued_asset( "JILLCOIN", jill, charge_market_fee, price, 2, jillcoin_market_percent ); + + // Alice and Bob create some coins + issue_uia( alice, izzycoin.amount( 100000 ) ); + issue_uia( bob, jillcoin.amount( 100000 ) ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(accumulated_fees_before_hf_test) +{ + try + { + INVOKE(issue_asset); + + const asset_object &jillcoin = get_asset("JILLCOIN"); + const asset_object &izzycoin = get_asset("IZZYCOIN"); + + GET_ACTOR(alice); + GET_ACTOR(bob); + + // Alice and Bob place orders which match + create_sell_order( alice_id, izzycoin.amount(100), jillcoin.amount(300) ); // Alice is willing to sell her Izzy's for 3 Jill + create_sell_order( bob_id, jillcoin.amount(700), izzycoin.amount(200) ); // Bob is buying up to 200 Izzy's for up to 3.5 Jill + + // 100 Izzys and 300 Jills are matched, so the fees should be + // 10 Izzy (10%) and 60 Jill (20%). + BOOST_CHECK( izzycoin.dynamic_asset_data_id(db).accumulated_fees == izzycoin.amount(10).amount ); + BOOST_CHECK( jillcoin.dynamic_asset_data_id(db).accumulated_fees == jillcoin.amount(60).amount ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(accumulated_fees_after_hf_test) +{ + try + { + INVOKE(issue_asset); + + generate_blocks_past_hf1268(); + + const asset_object &jillcoin = get_asset("JILLCOIN"); + const asset_object &izzycoin = get_asset("IZZYCOIN"); + + GET_ACTOR(alice); + GET_ACTOR(bob); + + // Alice and Bob place orders which match + create_sell_order( alice_id, izzycoin.amount(100), jillcoin.amount(300) ); // Alice is willing to sell her Izzy's for 3 Jill + create_sell_order( bob_id, jillcoin.amount(700), izzycoin.amount(200) ); // Bob is buying up to 200 Izzy's for up to 3.5 Jill + + // 100 Izzys and 300 Jills are matched, so the fees should be + // 10 Izzy (10%) and 60 Jill (20%). + BOOST_CHECK( izzycoin.dynamic_asset_data_id(db).accumulated_fees == izzycoin.amount(10).amount ); + BOOST_CHECK( jillcoin.dynamic_asset_data_id(db).accumulated_fees == jillcoin.amount(60).amount ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(accumulated_fees_with_additional_options_after_hf_test) +{ + try + { + INVOKE(issue_asset); + + generate_blocks_past_hf1268(); + + GET_ACTOR(jill); + GET_ACTOR(izzy); + + const asset_object &jillcoin = get_asset("JILLCOIN"); + const asset_object &izzycoin = get_asset("IZZYCOIN"); + + uint16_t reward_percent = 0; + update_asset(jill_id, jill_private_key, jillcoin.get_id(), reward_percent); + update_asset(izzy_id, izzy_private_key, izzycoin.get_id(), reward_percent); + + GET_ACTOR(alice); + GET_ACTOR(bob); + + // Alice and Bob place orders which match + create_sell_order( alice_id, izzycoin.amount(100), jillcoin.amount(300) ); // Alice is willing to sell her Izzy's for 3 Jill + create_sell_order( bob_id, jillcoin.amount(700), izzycoin.amount(200) ); // Bob is buying up to 200 Izzy's for up to 3.5 Jill + + // 100 Izzys and 300 Jills are matched, so the fees should be + // 10 Izzy (10%) and 60 Jill (20%). + BOOST_CHECK( izzycoin.dynamic_asset_data_id(db).accumulated_fees == izzycoin.amount(10).amount ); + BOOST_CHECK( jillcoin.dynamic_asset_data_id(db).accumulated_fees == jillcoin.amount(60).amount ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( create_vesting_balance_with_instant_vesting_policy_before_hf1268_test ) +{ try { + + ACTOR(alice); + fund(alice); + + const asset_object& core = asset_id_type()(db); + + vesting_balance_create_operation op; + op.fee = core.amount( 0 ); + op.creator = alice_id; + op.owner = alice_id; + op.amount = core.amount( 100 ); + op.policy = instant_vesting_policy_initializer{}; + + trx.operations.push_back(op); + set_expiration( db, trx ); + sign(trx, alice_private_key); + + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0 ), fc::exception); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( create_vesting_balance_with_instant_vesting_policy_after_hf1268_test ) +{ try { + + ACTOR(alice); + fund(alice); + + generate_blocks_past_hf1268(); + + const asset_object& core = asset_id_type()(db); + + vesting_balance_create_operation op; + op.fee = core.amount( 0 ); + op.creator = alice_id; + op.owner = alice_id; + op.amount = core.amount( 100 ); + op.policy = instant_vesting_policy_initializer{}; + + trx.operations.push_back(op); + set_expiration( db, trx ); + + processed_transaction ptx = PUSH_TX( db, trx, ~0 ); + const vesting_balance_id_type& vbid = ptx.operation_results.back().get(); + + auto withdraw = [&](const asset& amount) { + vesting_balance_withdraw_operation withdraw_op; + withdraw_op.vesting_balance = vbid; + withdraw_op.owner = alice_id; + withdraw_op.amount = amount; + + signed_transaction withdraw_tx; + withdraw_tx.operations.push_back( withdraw_op ); + set_expiration( db, withdraw_tx ); + sign(withdraw_tx, alice_private_key); + PUSH_TX( db, withdraw_tx ); + }; + // try to withdraw more then it is on the balance + GRAPHENE_REQUIRE_THROW(withdraw(op.amount.amount + 1), fc::exception); + //to withdraw all that is on the balance + withdraw(op.amount); + // try to withdraw more then it is on the balance + GRAPHENE_REQUIRE_THROW(withdraw( core.amount(1) ), fc::exception); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( create_vesting_balance_with_instant_vesting_policy_via_proposal_test ) +{ try { + + ACTOR(actor); + fund(actor); + + const asset_object& core = asset_id_type()(db); + + vesting_balance_create_operation create_op; + create_op.fee = core.amount( 0 ); + create_op.creator = actor_id; + create_op.owner = actor_id; + create_op.amount = core.amount( 100 ); + create_op.policy = instant_vesting_policy_initializer{}; + + const auto& curfees = *db.get_global_properties().parameters.current_fees; + const auto& proposal_create_fees = curfees.get(); + proposal_create_operation prop; + prop.fee_paying_account = actor_id; + prop.proposed_ops.emplace_back( create_op ); + prop.expiration_time = db.head_block_time() + fc::days(1); + prop.fee = asset( proposal_create_fees.fee + proposal_create_fees.price_per_kbyte ); + + { + signed_transaction tx; + tx.operations.push_back( prop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, actor_private_key ); + GRAPHENE_CHECK_THROW(PUSH_TX( db, tx ), fc::exception); + } + + generate_blocks_past_hf1268(); + + { + prop.expiration_time = db.head_block_time() + fc::days(1); + signed_transaction tx; + tx.operations.push_back( prop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, actor_private_key ); + PUSH_TX( db, tx ); + } +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(white_list_asset_rewards_test) +{ + try + { + ACTORS((aliceregistrar)(bobregistrar)(alicereferrer)(bobreferrer)(izzy)(jill)); + + // Izzy issues white_list asset to Alice + // Jill issues white_list asset to Bob + // Bobreferrer added to blacklist for izzycoin asset + // Aliceregistrar added to blacklist for jillcoin asset + // Alice and Bob trade in the market and pay fees + // Check registrar/referrer rewards + upgrade_to_lifetime_member(aliceregistrar); + upgrade_to_lifetime_member(alicereferrer); + upgrade_to_lifetime_member(bobregistrar); + upgrade_to_lifetime_member(bobreferrer); + upgrade_to_lifetime_member(izzy); + upgrade_to_lifetime_member(jill); + + const account_object alice = create_account("alice", aliceregistrar, alicereferrer, 20*GRAPHENE_1_PERCENT); + const account_object bob = create_account("bob", bobregistrar, bobreferrer, 20*GRAPHENE_1_PERCENT); + + fund( alice, core_asset(1000000) ); + fund( bob, core_asset(1000000) ); + fund( izzy, core_asset(1000000) ); + fund( jill, core_asset(1000000) ); + + price price(asset(1, asset_id_type(1)), asset(1)); + constexpr auto izzycoin_market_percent = 10*GRAPHENE_1_PERCENT; + constexpr auto jillcoin_market_percent = 20*GRAPHENE_1_PERCENT; + const asset_id_type izzycoin_id = create_user_issued_asset( "IZZYCOIN", izzy, charge_market_fee|white_list, price, 0, izzycoin_market_percent ).id; + const asset_id_type jillcoin_id = create_user_issued_asset( "JILLCOIN", jill, charge_market_fee|white_list, price, 0, jillcoin_market_percent ).id; + + // Alice and Bob create some coins + issue_uia( alice, izzycoin_id(db).amount( 200000 ) ); + issue_uia( bob, jillcoin_id(db).amount( 200000 ) ); + + generate_blocks_past_hf1268(); + + constexpr auto izzycoin_reward_percent = 50*GRAPHENE_1_PERCENT; + constexpr auto jillcoin_reward_percent = 50*GRAPHENE_1_PERCENT; + + update_asset(izzy_id, izzy_private_key, izzycoin_id, izzycoin_reward_percent); + update_asset(jill_id, jill_private_key, jillcoin_id, jillcoin_reward_percent); + + BOOST_TEST_MESSAGE( "Attempting to blacklist bobreferrer for izzycoin asset" ); + asset_update_blacklist_authority(izzy_id, izzycoin_id, izzy_id, izzy_private_key); + add_account_to_blacklist(izzy_id, bobreferrer_id, izzy_private_key); + BOOST_CHECK( !(is_authorized_asset( db, bobreferrer_id(db), izzycoin_id(db) )) ); + + BOOST_TEST_MESSAGE( "Attempting to blacklist aliceregistrar for jillcoin asset" ); + asset_update_blacklist_authority(jill_id, jillcoin_id, jill_id, jill_private_key); + add_account_to_blacklist(jill_id, aliceregistrar_id, jill_private_key); + BOOST_CHECK( !(is_authorized_asset( db, aliceregistrar_id(db), jillcoin_id(db) )) ); + + // Alice and Bob place orders which match + create_sell_order( alice.id, izzycoin_id(db).amount(1000), jillcoin_id(db).amount(1500) ); // Alice is willing to sell her 1000 Izzy's for 1.5 Jill + create_sell_order( bob.id, jillcoin_id(db).amount(1500), izzycoin_id(db).amount(1000) ); // Bob is buying up to 1500 Izzy's for up to 0.6 Jill + + // 1000 Izzys and 1500 Jills are matched, so the fees should be + // 100 Izzy (10%) and 300 Jill (20%). + + // Only Bob's registrar should get rewards + share_type bob_registrar_reward = get_market_fee_reward( bob.registrar, izzycoin_id ); + BOOST_CHECK_GT( bob_registrar_reward, 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( bob.referrer, izzycoin_id ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( alice.registrar, jillcoin_id ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( alice.referrer, jillcoin_id ), 0 ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( create_vesting_balance_object_test ) +{ + /** + * Test checks that an account could have duplicates VBO (with the same asset_type) + * for any type of vesting_balance_type + * except vesting_balance_type::market_fee_sharing + */ + try { + + ACTOR(actor); + + create_vesting_balance_object(actor_id, vesting_balance_type::unspecified); + create_vesting_balance_object(actor_id, vesting_balance_type::unspecified); + + create_vesting_balance_object(actor_id, vesting_balance_type::cashback); + create_vesting_balance_object(actor_id, vesting_balance_type::cashback); + + create_vesting_balance_object(actor_id, vesting_balance_type::witness); + create_vesting_balance_object(actor_id, vesting_balance_type::witness); + + create_vesting_balance_object(actor_id, vesting_balance_type::worker); + create_vesting_balance_object(actor_id, vesting_balance_type::worker); + + create_vesting_balance_object(actor_id, vesting_balance_type::market_fee_sharing); + GRAPHENE_CHECK_THROW(create_vesting_balance_object(actor_id, vesting_balance_type::market_fee_sharing), fc::exception); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index 1f29f0c843..c30505f87f 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -231,8 +231,14 @@ BOOST_AUTO_TEST_CASE(issue_338_etc) */ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) { try { + auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_343_TIME - mi); // assume all hard forks occur at same time + + if(hf1270) + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + else + generate_blocks(HARDFORK_CORE_343_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); @@ -325,7 +331,8 @@ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) // call's call_price will be updated after the match, to 741/31/1.75 CORE/USD = 2964/217 // it's above settlement price (10/1) so won't be margin called again - BOOST_CHECK( price(asset(2964),asset(217,usd_id)) == call.call_price ); + if(!hf1270) // can use call price only if we are before hf1270 + BOOST_CHECK( price(asset(2964),asset(217,usd_id)) == call.call_price ); // This would match with call before, but would match with call2 after #343 fixed BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700), core.amount(6000) ) ); @@ -342,7 +349,8 @@ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) BOOST_CHECK_EQUAL( 1000, call3.debt.value ); BOOST_CHECK_EQUAL( 16000, call3.collateral.value ); // call2's call_price will be updated after the match, to 78/3/1.75 CORE/USD = 312/21 - BOOST_CHECK( price(asset(312),asset(21,usd_id)) == call2.call_price ); + if(!hf1270) // can use call price only if we are before hf1270 + BOOST_CHECK( price(asset(312),asset(21,usd_id)) == call2.call_price ); // it's above settlement price (10/1) so won't be margin called // at this moment, collateralization of call is 7410 / 310 = 23.9 @@ -406,8 +414,14 @@ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) */ BOOST_AUTO_TEST_CASE(hardfork_core_453_test) { try { + auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_453_TIME - mi); // assume all hard forks occur at same time + + if(hf1270) + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + else + generate_blocks(HARDFORK_CORE_343_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); @@ -478,7 +492,6 @@ BOOST_AUTO_TEST_CASE(hardfork_core_453_test) // generate a block generate_block(); - } FC_LOG_AND_RETHROW() } /*** @@ -486,8 +499,14 @@ BOOST_AUTO_TEST_CASE(hardfork_core_453_test) */ BOOST_AUTO_TEST_CASE(hardfork_core_625_big_limit_order_test) { try { + auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_625_TIME - mi); // assume all hard forks occur at same time + + if(hf1270) + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + else + generate_blocks(HARDFORK_CORE_625_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); @@ -1195,8 +1214,14 @@ BOOST_AUTO_TEST_CASE(hard_fork_343_cross_test) */ BOOST_AUTO_TEST_CASE(target_cr_test_limit_call) { try { + auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_834_TIME - mi); + + if(hf1270) + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + else + generate_blocks(HARDFORK_CORE_834_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); @@ -1373,8 +1398,14 @@ BOOST_AUTO_TEST_CASE(target_cr_test_limit_call) */ BOOST_AUTO_TEST_CASE(target_cr_test_call_limit) { try { + auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_834_TIME - mi); + + if(hf1270) + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + else + generate_blocks(HARDFORK_CORE_834_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); @@ -1507,4 +1538,355 @@ BOOST_AUTO_TEST_CASE(target_cr_test_call_limit) } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE(mcr_bug_increase_before1270) +{ try { + + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_453_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + set_expiration( db, trx ); + + ACTORS((seller)(borrower)(borrower2)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + + int64_t init_balance(1000000); + + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + publish_feed( bitusd, feedproducer, current_feed ); + + const call_order_object& b1 = *borrow( borrower, bitusd.amount(1000), asset(1800)); + auto b1_id = b1.id; + const call_order_object& b2 = *borrow( borrower2, bitusd.amount(1000), asset(2000) ); + auto b2_id = b2.id; + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), init_balance - 1800 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), init_balance - 2000 ); + + // move order to margin call territory with mcr only + current_feed.maintenance_collateral_ratio = 2000; + publish_feed( bitusd, feedproducer, current_feed ); + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998200 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 998000 ); + + BOOST_CHECK( db.find( b1_id ) ); + BOOST_CHECK( db.find( b2_id ) ); + + // attempt to trade the margin call + create_sell_order( borrower2, bitusd.amount(1000), core.amount(1100) ); + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 0 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998200 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 998000 ); + + print_market(bitusd.symbol, core.symbol); + + // both calls are still there, no margin call, mcr bug + BOOST_CHECK( db.find( b1_id ) ); + BOOST_CHECK( db.find( b2_id ) ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(mcr_bug_increase_after1270) +{ try { + + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + set_expiration( db, trx ); + + ACTORS((seller)(borrower)(borrower2)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + + int64_t init_balance(1000000); + + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + publish_feed( bitusd, feedproducer, current_feed ); + + const call_order_object& b1 = *borrow( borrower, bitusd.amount(1000), asset(1800)); + auto b1_id = b1.id; + const call_order_object& b2 = *borrow( borrower2, bitusd.amount(1000), asset(2000) ); + auto b2_id = b2.id; + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), init_balance - 1800 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), init_balance - 2000 ); + + // move order to margin call territory with mcr only + current_feed.maintenance_collateral_ratio = 2000; + publish_feed( bitusd, feedproducer, current_feed ); + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998200 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 998000 ); + + BOOST_CHECK( db.find( b1_id ) ); + BOOST_CHECK( db.find( b2_id ) ); + + // attempt to trade the margin call + create_sell_order( borrower2, bitusd.amount(1000), core.amount(1100) ); + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 0 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998900 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 999100 ); + + print_market(bitusd.symbol, core.symbol); + + // b1 is margin called + BOOST_CHECK( ! db.find( b1_id ) ); + BOOST_CHECK( db.find( b2_id ) ); + + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(mcr_bug_decrease_before1270) +{ try { + + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_453_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + set_expiration( db, trx ); + + ACTORS((seller)(borrower)(borrower2)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + + int64_t init_balance(1000000); + + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + publish_feed( bitusd, feedproducer, current_feed ); + + const call_order_object& b1 = *borrow( borrower, bitusd.amount(1000), asset(1800)); + auto b1_id = b1.id; + const call_order_object& b2 = *borrow( borrower2, bitusd.amount(1000), asset(2000) ); + auto b2_id = b2.id; + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), init_balance - 1800 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), init_balance - 2000 ); + + // move order to margin call territory with the feed + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(150); + publish_feed( bitusd, feedproducer, current_feed ); + + // getting out of margin call territory with mcr change + current_feed.maintenance_collateral_ratio = 1100; + publish_feed( bitusd, feedproducer, current_feed ); + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998200 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 998000 ); + + BOOST_CHECK( db.find( b1_id ) ); + BOOST_CHECK( db.find( b2_id ) ); + + // attempt to trade the margin call + create_sell_order( borrower2, bitusd.amount(1000), core.amount(1100) ); + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 0 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998350 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 999650 ); + + print_market(bitusd.symbol, core.symbol); + + // margin call at b1, mcr bug + BOOST_CHECK( !db.find( b1_id ) ); + BOOST_CHECK( db.find( b2_id ) ); + + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(mcr_bug_decrease_after1270) +{ try { + + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + set_expiration( db, trx ); + + ACTORS((seller)(borrower)(borrower2)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + + int64_t init_balance(1000000); + + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + publish_feed( bitusd, feedproducer, current_feed ); + + const call_order_object& b1 = *borrow( borrower, bitusd.amount(1000), asset(1800)); + auto b1_id = b1.id; + const call_order_object& b2 = *borrow( borrower2, bitusd.amount(1000), asset(2000) ); + auto b2_id = b2.id; + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), init_balance - 1800 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), init_balance - 2000 ); + + // move order to margin call territory with the feed + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(150); + publish_feed( bitusd, feedproducer, current_feed ); + + // getting out of margin call territory with mcr decrease + current_feed.maintenance_collateral_ratio = 1100; + publish_feed( bitusd, feedproducer, current_feed ); + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998200 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 998000 ); + + BOOST_CHECK( db.find( b1_id ) ); + BOOST_CHECK( db.find( b2_id ) ); + + // attempt to trade the margin call + create_sell_order( borrower2, bitusd.amount(1000), core.amount(1100) ); + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 0 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998200 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 998000 ); + + print_market(bitusd.symbol, core.symbol); + + // both calls are there, no margin call, good + BOOST_CHECK( db.find( b1_id ) ); + BOOST_CHECK( db.find( b2_id ) ); + + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(mcr_bug_cross1270) +{ try { + + INVOKE(mcr_bug_increase_before1270); + + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + + const asset_object& core = get_asset(GRAPHENE_SYMBOL); + const asset_object& bitusd = get_asset("USDBIT"); + const asset_id_type bitusd_id = bitusd.id; + const account_object& feedproducer = get_account("feedproducer"); + + // feed is expired + auto mcr = (*bitusd_id(db).bitasset_data_id)(db).current_feed.maintenance_collateral_ratio; + BOOST_CHECK_EQUAL(mcr, GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO); + + // make new feed + price_feed current_feed; + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100); + current_feed.maintenance_collateral_ratio = 2000; + current_feed.maximum_short_squeeze_ratio = 1100; + publish_feed( bitusd, feedproducer, current_feed ); + + mcr = (*bitusd_id(db).bitasset_data_id)(db).current_feed.maintenance_collateral_ratio; + BOOST_CHECK_EQUAL(mcr, 2000); + + // pass hardfork + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + // feed is still valid + mcr = (*bitusd_id(db).bitasset_data_id)(db).current_feed.maintenance_collateral_ratio; + BOOST_CHECK_EQUAL(mcr, 2000); + + // margin call is traded + print_market(asset_id_type(1)(db).symbol, asset_id_type()(db).symbol); + + // call b1 not there anymore + BOOST_CHECK( !db.find( call_order_id_type() ) ); + BOOST_CHECK( db.find( call_order_id_type(1) ) ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(hardfork_core_338_test_after_hf1270) +{ try { + hf1270 = true; + INVOKE(hardfork_core_338_test); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(hardfork_core_453_test_after_hf1270) +{ try { + hf1270 = true; + INVOKE(hardfork_core_453_test); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(hardfork_core_625_big_limit_order_test_after_hf1270) +{ try { + hf1270 = true; + INVOKE(hardfork_core_625_big_limit_order_test); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(target_cr_test_limit_call_after_hf1270) +{ try { + hf1270 = true; + INVOKE(target_cr_test_limit_call); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(target_cr_test_call_limit_after_hf1270) +{ try { + hf1270 = true; + INVOKE(target_cr_test_call_limit); + +} FC_LOG_AND_RETHROW() } + + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/network_broadcast_api_tests.cpp b/tests/tests/network_broadcast_api_tests.cpp index a566750b67..a40c112662 100644 --- a/tests/tests/network_broadcast_api_tests.cpp +++ b/tests/tests/network_broadcast_api_tests.cpp @@ -25,6 +25,7 @@ #include #include +#include #include @@ -71,4 +72,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() diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index b2edab15ad..c4fa17b8a9 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -592,7 +592,6 @@ BOOST_AUTO_TEST_CASE( call_order_update_validation_test ) op.extensions.value.target_collateral_ratio = 65535; op.validate(); // still valid - } // Tests that target_cr option can't be set before hard fork core-834 @@ -2004,6 +2003,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. diff --git a/tests/tests/swan_tests.cpp b/tests/tests/swan_tests.cpp index 30031379c0..54f233c66a 100644 --- a/tests/tests/swan_tests.cpp +++ b/tests/tests/swan_tests.cpp @@ -114,6 +114,11 @@ struct swan_fixture : database_fixture { generate_blocks( HARDFORK_CORE_216_TIME ); generate_block(); } + void wait_for_hf_core_1270() { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + wait_for_maintenance(); + } void wait_for_maintenance() { generate_blocks( db.get_dynamic_global_properties().next_maintenance_time ); @@ -141,6 +146,9 @@ BOOST_FIXTURE_TEST_SUITE( swan_tests, swan_fixture ) */ BOOST_AUTO_TEST_CASE( black_swan ) { try { + if(hf1270) + wait_for_hf_core_1270(); + init_standard_swan(); force_settle( borrower(), swan().amount(100) ); @@ -167,6 +175,7 @@ BOOST_AUTO_TEST_CASE( black_swan ) */ BOOST_AUTO_TEST_CASE( black_swan_issue_346 ) { try { + ACTORS((buyer)(seller)(borrower)(borrower2)(settler)(feeder)); const asset_object& core = asset_id_type()(db); @@ -246,11 +255,11 @@ BOOST_AUTO_TEST_CASE( black_swan_issue_346 ) force_settle( settler, bitusd.amount(100) ); // wait for forced settlement to execute - // this would throw on Sep.18 testnet, see #346 + // this would throw on Sep.18 testnet, see #346 (https://github.com/cryptonomex/graphene/issues/346) wait_for_settlement(); } - // issue 350 + // issue 350 (https://github.com/cryptonomex/graphene/issues/350) { // ok, new asset const asset_object& bitusd = setup_asset(); @@ -282,7 +291,10 @@ BOOST_AUTO_TEST_CASE( revive_recovered ) { try { init_standard_swan( 700 ); - wait_for_hf_core_216(); + if(hf1270) + wait_for_hf_core_1270(); + else + wait_for_hf_core_216(); // revive after price recovers set_feed( 700, 800 ); @@ -304,7 +316,10 @@ BOOST_AUTO_TEST_CASE( recollateralize ) // no hardfork yet GRAPHENE_REQUIRE_THROW( bid_collateral( borrower2(), back().amount(1000), swan().amount(100) ), fc::exception ); - wait_for_hf_core_216(); + if(hf1270) + wait_for_hf_core_1270(); + else + wait_for_hf_core_216(); int64_t b2_balance = get_balance( borrower2(), back() ); bid_collateral( borrower2(), back().amount(1000), swan().amount(100) ); @@ -397,7 +412,10 @@ BOOST_AUTO_TEST_CASE( revive_empty_recovered ) { try { limit_order_id_type oid = init_standard_swan( 1000 ); - wait_for_hf_core_216(); + if(hf1270) + wait_for_hf_core_1270(); + else + wait_for_hf_core_216(); set_expiration( db, trx ); cancel_limit_order( oid(db) ); @@ -424,7 +442,10 @@ BOOST_AUTO_TEST_CASE( revive_empty_recovered ) */ BOOST_AUTO_TEST_CASE( revive_empty ) { try { - wait_for_hf_core_216(); + if(hf1270) + wait_for_hf_core_1270(); + else + wait_for_hf_core_216(); limit_order_id_type oid = init_standard_swan( 1000 ); @@ -448,7 +469,10 @@ BOOST_AUTO_TEST_CASE( revive_empty ) */ BOOST_AUTO_TEST_CASE( revive_empty_with_bid ) { try { - wait_for_hf_core_216(); + if(hf1270) + wait_for_hf_core_1270(); + else + wait_for_hf_core_216(); standard_users(); standard_asset(); @@ -493,6 +517,50 @@ BOOST_AUTO_TEST_CASE( revive_empty_with_bid ) } } +BOOST_AUTO_TEST_CASE(black_swan_after_hf1270) +{ try { + hf1270 = true; + INVOKE(black_swan); + +} FC_LOG_AND_RETHROW() } + +// black_swan_issue_346_hf1270 is skipped as it is already failing with HARDFORK_CORE_834_TIME + +BOOST_AUTO_TEST_CASE(revive_recovered_hf1270) +{ try { + hf1270 = true; + INVOKE(revive_recovered); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(recollateralize_hf1270) +{ try { + hf1270 = true; + INVOKE(recollateralize); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(revive_empty_recovered_hf1270) +{ try { + hf1270 = true; + INVOKE(revive_empty_recovered); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(revive_empty_hf1270) +{ try { + hf1270 = true; + INVOKE(revive_empty); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(revive_empty_with_bid_hf1270) +{ try { + hf1270 = true; + INVOKE(revive_empty_with_bid); + +} FC_LOG_AND_RETHROW() } + /** Creates a black swan, bids on more than outstanding debt */ BOOST_AUTO_TEST_CASE( overflow ) diff --git a/tests/tests/uia_tests.cpp b/tests/tests/uia_tests.cpp index 56b2116a44..0efc4c5ef9 100644 --- a/tests/tests/uia_tests.cpp +++ b/tests/tests/uia_tests.cpp @@ -58,6 +58,7 @@ BOOST_AUTO_TEST_CASE( create_advanced_uia ) creator.common_options.flags = charge_market_fee|white_list|override_authority|disable_confidential; creator.common_options.core_exchange_rate = price(asset(2),asset(1,asset_id_type(1))); creator.common_options.whitelist_authorities = creator.common_options.blacklist_authorities = {account_id_type()}; + trx.operations.push_back(std::move(creator)); PUSH_TX( db, trx, ~0 ); @@ -73,6 +74,7 @@ BOOST_AUTO_TEST_CASE( create_advanced_uia ) BOOST_CHECK(test_asset_dynamic_data.current_supply == 0); BOOST_CHECK(test_asset_dynamic_data.accumulated_fees == 0); BOOST_CHECK(test_asset_dynamic_data.fee_pool == 0); + } catch(fc::exception& e) { edump((e.to_detail_string())); throw;