Merge branch '538-fork-buyback' into develop
Includes dependencies '555-fork-defer-s4n-cull', '559-test-fix-zero-block-wait' and '566-cleanup-wl-flag-check'
This commit is contained in:
commit
e46af9f372
35 changed files with 756 additions and 156 deletions
|
|
@ -346,6 +346,8 @@ namespace graphene { namespace app {
|
|||
break;
|
||||
case impl_special_authority_object_type:
|
||||
break;
|
||||
case impl_buyback_object_type:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ add_library( graphene_chain
|
|||
worker_evaluator.cpp
|
||||
confidential_evaluator.cpp
|
||||
special_authority.cpp
|
||||
buyback.cpp
|
||||
|
||||
account_object.cpp
|
||||
asset_object.cpp
|
||||
|
|
@ -85,6 +86,8 @@ add_library( graphene_chain
|
|||
|
||||
block_database.cpp
|
||||
|
||||
is_authorized_asset.cpp
|
||||
|
||||
${HEADERS}
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/include/graphene/chain/hardfork.hpp"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@
|
|||
#include <fc/smart_ref_impl.hpp>
|
||||
|
||||
#include <graphene/chain/account_evaluator.hpp>
|
||||
#include <graphene/chain/buyback.hpp>
|
||||
#include <graphene/chain/buyback_object.hpp>
|
||||
#include <graphene/chain/database.hpp>
|
||||
#include <graphene/chain/exceptions.hpp>
|
||||
#include <graphene/chain/hardfork.hpp>
|
||||
|
|
@ -80,6 +82,8 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio
|
|||
evaluate_special_authority( d, *op.extensions.value.owner_special_authority );
|
||||
if( op.extensions.value.active_special_authority.valid() )
|
||||
evaluate_special_authority( d, *op.extensions.value.active_special_authority );
|
||||
if( op.extensions.value.buyback_options.valid() )
|
||||
evaluate_buyback_account_options( d, *op.extensions.value.buyback_options );
|
||||
|
||||
uint32_t max_vote_id = global_props.next_available_vote_id;
|
||||
|
||||
|
|
@ -115,6 +119,7 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio
|
|||
object_id_type account_create_evaluator::do_apply( const account_create_operation& o )
|
||||
{ try {
|
||||
|
||||
database& d = db();
|
||||
uint16_t referrer_percent = o.referrer_percent;
|
||||
bool has_small_percent = (
|
||||
(db().head_block_time() <= HARDFORK_453_TIME)
|
||||
|
|
@ -154,6 +159,11 @@ object_id_type account_create_evaluator::do_apply( const account_create_operatio
|
|||
obj.owner_special_authority = *(o.extensions.value.owner_special_authority);
|
||||
if( o.extensions.value.active_special_authority.valid() )
|
||||
obj.active_special_authority = *(o.extensions.value.active_special_authority);
|
||||
if( o.extensions.value.buyback_options.valid() )
|
||||
{
|
||||
obj.allowed_assets = o.extensions.value.buyback_options->markets;
|
||||
obj.allowed_assets->emplace( o.extensions.value.buyback_options->asset_to_buy );
|
||||
}
|
||||
});
|
||||
|
||||
if( has_small_percent )
|
||||
|
|
@ -186,6 +196,21 @@ object_id_type account_create_evaluator::do_apply( const account_create_operatio
|
|||
} );
|
||||
}
|
||||
|
||||
if( o.extensions.value.buyback_options.valid() )
|
||||
{
|
||||
asset_id_type asset_to_buy = o.extensions.value.buyback_options->asset_to_buy;
|
||||
|
||||
d.create< buyback_object >( [&]( buyback_object& bo )
|
||||
{
|
||||
bo.asset_to_buy = asset_to_buy;
|
||||
} );
|
||||
|
||||
d.modify( asset_to_buy(d), [&]( asset_object& a )
|
||||
{
|
||||
a.buyback_account = new_acnt_object.id;
|
||||
} );
|
||||
}
|
||||
|
||||
return new_acnt_object.id;
|
||||
} FC_CAPTURE_AND_RETHROW((o)) }
|
||||
|
||||
|
|
|
|||
|
|
@ -42,35 +42,6 @@ share_type cut_fee(share_type a, uint16_t p)
|
|||
return r.to_uint64();
|
||||
}
|
||||
|
||||
bool account_object::is_authorized_asset(const asset_object& asset_obj, const database& d) const
|
||||
{
|
||||
if( d.head_block_time() > HARDFORK_416_TIME )
|
||||
{
|
||||
if( !(asset_obj.options.flags & white_list) )
|
||||
return true;
|
||||
}
|
||||
|
||||
for( const auto id : blacklisting_accounts )
|
||||
{
|
||||
if( asset_obj.options.blacklist_authorities.find(id) != asset_obj.options.blacklist_authorities.end() )
|
||||
return false;
|
||||
}
|
||||
|
||||
if( d.head_block_time() > HARDFORK_415_TIME )
|
||||
{
|
||||
if( asset_obj.options.whitelist_authorities.size() == 0 )
|
||||
return true;
|
||||
}
|
||||
|
||||
for( const auto id : whitelisting_accounts )
|
||||
{
|
||||
if( asset_obj.options.whitelist_authorities.find(id) != asset_obj.options.whitelist_authorities.end() )
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void account_balance_object::adjust_balance(const asset& delta)
|
||||
{
|
||||
assert(delta.asset_id == asset_type);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
#include <graphene/chain/database.hpp>
|
||||
#include <graphene/chain/exceptions.hpp>
|
||||
#include <graphene/chain/hardfork.hpp>
|
||||
#include <graphene/chain/is_authorized_asset.hpp>
|
||||
|
||||
#include <functional>
|
||||
|
||||
|
|
@ -152,11 +153,7 @@ void_result asset_issue_evaluator::do_evaluate( const asset_issue_operation& o )
|
|||
FC_ASSERT( !a.is_market_issued(), "Cannot manually issue a market-issued asset." );
|
||||
|
||||
to_account = &o.issue_to_account(d);
|
||||
|
||||
if( a.options.flags & white_list )
|
||||
{
|
||||
FC_ASSERT( to_account->is_authorized_asset( a, d ) );
|
||||
}
|
||||
FC_ASSERT( is_authorized_asset( d, *to_account, a ) );
|
||||
|
||||
asset_dyn_data = &a.dynamic_asset_data_id(d);
|
||||
FC_ASSERT( (asset_dyn_data->current_supply + o.asset_to_issue.amount) <= a.options.max_supply );
|
||||
|
|
@ -188,11 +185,7 @@ void_result asset_reserve_evaluator::do_evaluate( const asset_reserve_operation&
|
|||
);
|
||||
|
||||
from_account = &o.payer(d);
|
||||
|
||||
if( a.options.flags & white_list )
|
||||
{
|
||||
FC_ASSERT( from_account->is_authorized_asset( a, d ) );
|
||||
}
|
||||
FC_ASSERT( is_authorized_asset( d, *from_account, a ) );
|
||||
|
||||
asset_dyn_data = &a.dynamic_asset_data_id(d);
|
||||
FC_ASSERT( (asset_dyn_data->current_supply - o.amount_to_reserve.amount) >= 0 );
|
||||
|
|
|
|||
45
libraries/chain/buyback.cpp
Normal file
45
libraries/chain/buyback.cpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
*
|
||||
* The MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <graphene/chain/protocol/buyback.hpp>
|
||||
#include <graphene/chain/database.hpp>
|
||||
#include <graphene/chain/exceptions.hpp>
|
||||
#include <graphene/chain/hardfork.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
void evaluate_buyback_account_options( const database& db, const buyback_account_options& bbo )
|
||||
{
|
||||
FC_ASSERT( db.head_block_time() >= HARDFORK_538_TIME );
|
||||
const asset_object& a = bbo.asset_to_buy(db);
|
||||
GRAPHENE_ASSERT( a.issuer == bbo.asset_to_buy_issuer,
|
||||
account_create_buyback_incorrect_issuer, "Incorrect asset issuer specified in buyback_account_options", ("asset", a)("bbo", bbo) );
|
||||
GRAPHENE_ASSERT( !a.buyback_account.valid(),
|
||||
account_create_buyback_already_exists, "Cannot create buyback for asset which already has buyback", ("asset", a)("bbo", bbo) );
|
||||
// TODO: Replace with chain parameter #554
|
||||
GRAPHENE_ASSERT( bbo.markets.size() < GRAPHENE_DEFAULT_MAX_BUYBACK_MARKETS,
|
||||
account_create_buyback_too_many_markets, "Too many buyback markets", ("asset", a)("bbo", bbo) );
|
||||
}
|
||||
|
||||
} }
|
||||
|
|
@ -296,14 +296,14 @@ signed_block database::generate_block(
|
|||
const fc::ecc::private_key& block_signing_private_key,
|
||||
uint32_t skip /* = 0 */
|
||||
)
|
||||
{
|
||||
{ try {
|
||||
signed_block result;
|
||||
detail::with_skip_flags( *this, skip, [&]()
|
||||
{
|
||||
result = _generate_block( when, witness_id, block_signing_private_key );
|
||||
} );
|
||||
return result;
|
||||
}
|
||||
} FC_CAPTURE_AND_RETHROW() }
|
||||
|
||||
signed_block database::_generate_block(
|
||||
fc::time_point_sec when,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include <graphene/chain/balance_object.hpp>
|
||||
#include <graphene/chain/block_summary_object.hpp>
|
||||
#include <graphene/chain/budget_record_object.hpp>
|
||||
#include <graphene/chain/buyback_object.hpp>
|
||||
#include <graphene/chain/chain_property_object.hpp>
|
||||
#include <graphene/chain/committee_member_object.hpp>
|
||||
#include <graphene/chain/confidential_object.hpp>
|
||||
|
|
@ -210,6 +211,7 @@ void database::initialize_indexes()
|
|||
add_index< primary_index<simple_index<witness_schedule_object > > >();
|
||||
add_index< primary_index<simple_index<budget_record_object > > >();
|
||||
add_index< primary_index< special_authority_index > >();
|
||||
add_index< primary_index< buyback_index > >();
|
||||
}
|
||||
|
||||
void database::init_genesis(const genesis_state_type& genesis_state)
|
||||
|
|
|
|||
|
|
@ -33,9 +33,11 @@
|
|||
#include <graphene/chain/account_object.hpp>
|
||||
#include <graphene/chain/asset_object.hpp>
|
||||
#include <graphene/chain/budget_record_object.hpp>
|
||||
#include <graphene/chain/buyback_object.hpp>
|
||||
#include <graphene/chain/chain_property_object.hpp>
|
||||
#include <graphene/chain/committee_member_object.hpp>
|
||||
#include <graphene/chain/global_property_object.hpp>
|
||||
#include <graphene/chain/market_object.hpp>
|
||||
#include <graphene/chain/special_authority_object.hpp>
|
||||
#include <graphene/chain/vesting_balance_object.hpp>
|
||||
#include <graphene/chain/vote_count.hpp>
|
||||
|
|
@ -515,10 +517,88 @@ void update_top_n_authorities( database& db )
|
|||
} );
|
||||
}
|
||||
|
||||
void create_buyback_orders( database& db )
|
||||
{
|
||||
const auto& bbo_idx = db.get_index_type< buyback_index >().indices().get<by_id>();
|
||||
const auto& bal_idx = db.get_index_type< account_balance_index >().indices().get< by_account_asset >();
|
||||
|
||||
for( const buyback_object& bbo : bbo_idx )
|
||||
{
|
||||
const asset_object& asset_to_buy = bbo.asset_to_buy(db);
|
||||
assert( asset_to_buy.buyback_account.valid() );
|
||||
|
||||
const account_object& buyback_account = (*(asset_to_buy.buyback_account))(db);
|
||||
asset_id_type next_asset = asset_id_type();
|
||||
|
||||
if( !buyback_account.allowed_assets.valid() )
|
||||
{
|
||||
wlog( "skipping buyback account ${b} at block ${n} because allowed_assets does not exist", ("b", buyback_account)("n", db.head_block_num()) );
|
||||
continue;
|
||||
}
|
||||
|
||||
while( true )
|
||||
{
|
||||
auto it = bal_idx.lower_bound( boost::make_tuple( buyback_account.id, next_asset ) );
|
||||
if( it == bal_idx.end() )
|
||||
break;
|
||||
if( it->owner != buyback_account.id )
|
||||
break;
|
||||
asset_id_type asset_to_sell = it->asset_type;
|
||||
share_type amount_to_sell = it->balance;
|
||||
next_asset = asset_to_sell + 1;
|
||||
if( asset_to_sell == asset_to_buy.id )
|
||||
continue;
|
||||
if( amount_to_sell == 0 )
|
||||
continue;
|
||||
if( buyback_account.allowed_assets->find( asset_to_sell ) == buyback_account.allowed_assets->end() )
|
||||
{
|
||||
wlog( "buyback account ${b} not selling disallowed holdings of asset ${a} at block ${n}", ("b", buyback_account)("a", asset_to_sell)("n", db.head_block_num()) );
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
transaction_evaluation_state buyback_context(&db);
|
||||
buyback_context.skip_fee_schedule_check = true;
|
||||
|
||||
limit_order_create_operation create_vop;
|
||||
create_vop.fee = asset( 0, asset_id_type() );
|
||||
create_vop.seller = buyback_account.id;
|
||||
create_vop.amount_to_sell = asset( amount_to_sell, asset_to_sell );
|
||||
create_vop.min_to_receive = asset( 1, asset_to_buy.id );
|
||||
create_vop.expiration = time_point_sec::maximum();
|
||||
create_vop.fill_or_kill = false;
|
||||
|
||||
limit_order_id_type order_id = db.apply_operation( buyback_context, create_vop ).get< object_id_type >();
|
||||
|
||||
if( db.find( order_id ) != nullptr )
|
||||
{
|
||||
limit_order_cancel_operation cancel_vop;
|
||||
cancel_vop.fee = asset( 0, asset_id_type() );
|
||||
cancel_vop.order = order_id;
|
||||
cancel_vop.fee_paying_account = buyback_account.id;
|
||||
|
||||
db.apply_operation( buyback_context, cancel_vop );
|
||||
}
|
||||
}
|
||||
catch( const fc::exception& e )
|
||||
{
|
||||
// we can in fact get here, e.g. if asset issuer of buy/sell asset blacklists/whitelists the buyback account
|
||||
wlog( "Skipping buyback processing selling ${as} for ${ab} for buyback account ${b} at block ${n}; exception was ${e}",
|
||||
("as", asset_to_sell)("ab", asset_to_buy)("b", buyback_account)("n", db.head_block_num())("e", e.to_detail_string()) );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props)
|
||||
{
|
||||
const auto& gpo = get_global_properties();
|
||||
|
||||
create_buyback_orders(*this);
|
||||
|
||||
struct vote_tally_helper {
|
||||
database& d;
|
||||
const global_property_object& props;
|
||||
|
|
|
|||
|
|
@ -132,6 +132,27 @@ void database::cancel_order( const limit_order_object& order, bool create_virtua
|
|||
remove(order);
|
||||
}
|
||||
|
||||
bool maybe_cull_small_order( database& db, const limit_order_object& order )
|
||||
{
|
||||
/**
|
||||
* There are times when the AMOUNT_FOR_SALE * SALE_PRICE == 0 which means that we
|
||||
* have hit the limit where the seller is asking for nothing in return. When this
|
||||
* happens we must refund any balance back to the seller, it is too small to be
|
||||
* sold at the sale price.
|
||||
*
|
||||
* If the order is a taker order (as opposed to a maker order), so the price is
|
||||
* set by the counterparty, this check is deferred until the order becomes unmatched
|
||||
* (see #555) -- however, detecting this condition is the responsibility of the caller.
|
||||
*/
|
||||
if( order.amount_to_receive().amount == 0 )
|
||||
{
|
||||
ilog( "applied epsilon logic" );
|
||||
db.cancel_order(order);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool database::apply_order(const limit_order_object& new_order_object, bool allow_black_swan)
|
||||
{
|
||||
auto order_id = new_order_object.id;
|
||||
|
|
@ -171,7 +192,15 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo
|
|||
check_call_orders(sell_asset, allow_black_swan);
|
||||
check_call_orders(receive_asset, allow_black_swan);
|
||||
|
||||
return find_object(order_id) == nullptr;
|
||||
const limit_order_object* updated_order_object = find< limit_order_object >( order_id );
|
||||
if( updated_order_object == nullptr )
|
||||
return true;
|
||||
if( head_block_time() <= HARDFORK_555_TIME )
|
||||
return false;
|
||||
// before #555 we would have done maybe_cull_small_order() logic as a result of fill_order() being called by match() above
|
||||
// however after #555 we need to get rid of small orders -- #555 hardfork defers logic that was done too eagerly before, and
|
||||
// this is the point it's deferred to.
|
||||
return maybe_cull_small_order( *this, *updated_order_object );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -218,8 +247,8 @@ int database::match( const limit_order_object& usd, const OrderType& core, const
|
|||
core_pays == core.amount_for_sale() );
|
||||
|
||||
int result = 0;
|
||||
result |= fill_order( usd, usd_pays, usd_receives );
|
||||
result |= fill_order( core, core_pays, core_receives ) << 1;
|
||||
result |= fill_order( usd, usd_pays, usd_receives, false );
|
||||
result |= fill_order( core, core_pays, core_receives, true ) << 1;
|
||||
assert( result != 0 );
|
||||
return result;
|
||||
}
|
||||
|
|
@ -263,8 +292,10 @@ asset database::match( const call_order_object& call,
|
|||
return call_receives;
|
||||
} FC_CAPTURE_AND_RETHROW( (call)(settle)(match_price)(max_settlement) ) }
|
||||
|
||||
bool database::fill_order( const limit_order_object& order, const asset& pays, const asset& receives )
|
||||
bool database::fill_order( const limit_order_object& order, const asset& pays, const asset& receives, bool cull_if_small )
|
||||
{ try {
|
||||
cull_if_small |= (head_block_time() < HARDFORK_555_TIME);
|
||||
|
||||
FC_ASSERT( order.amount_for_sale().asset_id == pays.asset_id );
|
||||
FC_ASSERT( pays.asset_id != receives.asset_id );
|
||||
|
||||
|
|
@ -297,17 +328,8 @@ bool database::fill_order( const limit_order_object& order, const asset& pays, c
|
|||
b.for_sale -= pays.amount;
|
||||
b.deferred_fee = 0;
|
||||
});
|
||||
/**
|
||||
* There are times when the AMOUNT_FOR_SALE * SALE_PRICE == 0 which means that we
|
||||
* have hit the limit where the seller is asking for nothing in return. When this
|
||||
* happens we must refund any balance back to the seller, it is too small to be
|
||||
* sold at the sale price.
|
||||
*/
|
||||
if( order.amount_to_receive().amount == 0 )
|
||||
{
|
||||
cancel_order(order);
|
||||
return true;
|
||||
}
|
||||
if( cull_if_small )
|
||||
return maybe_cull_small_order( *this, order );
|
||||
return false;
|
||||
}
|
||||
} FC_CAPTURE_AND_RETHROW( (order)(pays)(receives) ) }
|
||||
|
|
@ -517,7 +539,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan
|
|||
fill_order(*old_call_itr, call_pays, call_receives);
|
||||
|
||||
auto old_limit_itr = filled_limit ? limit_itr++ : limit_itr;
|
||||
fill_order(*old_limit_itr, order_pays, order_receives);
|
||||
fill_order(*old_limit_itr, order_pays, order_receives, true);
|
||||
|
||||
} // whlie call_itr != call_end
|
||||
|
||||
|
|
|
|||
|
|
@ -156,14 +156,14 @@ void database::update_last_irreversible_block()
|
|||
}
|
||||
|
||||
void database::clear_expired_transactions()
|
||||
{
|
||||
{ try {
|
||||
//Look for expired transactions in the deduplication list, and remove them.
|
||||
//Transactions must have expired by at least two forking windows in order to be removed.
|
||||
auto& transaction_idx = static_cast<transaction_index&>(get_mutable_index(implementation_ids, impl_transaction_object_type));
|
||||
const auto& dedupe_index = transaction_idx.indices().get<by_expiration>();
|
||||
while( (!dedupe_index.empty()) && (head_block_time() > dedupe_index.rbegin()->trx.expiration) )
|
||||
transaction_idx.remove(*dedupe_index.rbegin());
|
||||
}
|
||||
} FC_CAPTURE_AND_RETHROW() }
|
||||
|
||||
void database::clear_expired_proposals()
|
||||
{
|
||||
|
|
@ -254,7 +254,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s
|
|||
}
|
||||
|
||||
void database::clear_expired_orders()
|
||||
{
|
||||
{ try {
|
||||
detail::with_skip_flags( *this,
|
||||
get_node_properties().skip_flags | skip_authority_check, [&](){
|
||||
transaction_evaluation_state cancel_context(this);
|
||||
|
|
@ -267,6 +267,18 @@ void database::clear_expired_orders()
|
|||
const limit_order_object& order = *limit_index.begin();
|
||||
canceler.fee_paying_account = order.seller;
|
||||
canceler.order = order.id;
|
||||
canceler.fee = current_fee_schedule().calculate_fee( canceler );
|
||||
if( canceler.fee.amount > order.deferred_fee )
|
||||
{
|
||||
// Cap auto-cancel fees at deferred_fee; see #549
|
||||
wlog( "At block ${b}, fee for clearing expired order ${oid} was capped at deferred_fee ${fee}", ("b", head_block_num())("oid", order.id)("fee", order.deferred_fee) );
|
||||
canceler.fee = asset( order.deferred_fee, asset_id_type() );
|
||||
}
|
||||
// we know the fee for this op is set correctly since it is set by the chain.
|
||||
// this allows us to avoid a hung chain:
|
||||
// - if #549 case above triggers
|
||||
// - if the fee is incorrect, which may happen due to #435 (although since cancel is a fixed-fee op, it shouldn't)
|
||||
cancel_context.skip_fee_schedule_check = true;
|
||||
apply_operation(cancel_context, canceler);
|
||||
}
|
||||
});
|
||||
|
|
@ -409,7 +421,7 @@ void database::clear_expired_orders()
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} FC_CAPTURE_AND_RETHROW() }
|
||||
|
||||
void database::update_expired_feeds()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include <graphene/chain/evaluator.hpp>
|
||||
#include <graphene/chain/exceptions.hpp>
|
||||
#include <graphene/chain/hardfork.hpp>
|
||||
#include <graphene/chain/is_authorized_asset.hpp>
|
||||
#include <graphene/chain/transaction_evaluation_state.hpp>
|
||||
|
||||
#include <graphene/chain/asset_object.hpp>
|
||||
|
|
@ -61,7 +62,7 @@ database& generic_evaluator::db()const { return trx_state->db(); }
|
|||
|
||||
if( d.head_block_time() > HARDFORK_419_TIME )
|
||||
{
|
||||
FC_ASSERT( fee_paying_account->is_authorized_asset( *fee_asset, d ), "Account ${acct} '${name}' attempted to pay fee by using asset ${a} '${sym}', which is unauthorized due to whitelist / blacklist",
|
||||
FC_ASSERT( is_authorized_asset( d, *fee_paying_account, *fee_asset ), "Account ${acct} '${name}' attempted to pay fee by using asset ${a} '${sym}', which is unauthorized due to whitelist / blacklist",
|
||||
("acct", fee_paying_account->id)("name", fee_paying_account->name)("a", fee_asset->id)("sym", fee_asset->symbol) );
|
||||
}
|
||||
|
||||
|
|
@ -79,24 +80,27 @@ database& generic_evaluator::db()const { return trx_state->db(); }
|
|||
|
||||
void generic_evaluator::convert_fee()
|
||||
{
|
||||
if( fee_asset->get_id() != asset_id_type() )
|
||||
{
|
||||
db().modify(*fee_asset_dyn_data, [this](asset_dynamic_data_object& d) {
|
||||
d.accumulated_fees += fee_from_account.amount;
|
||||
d.fee_pool -= core_fee_paid;
|
||||
});
|
||||
if( !trx_state->skip_fee ) {
|
||||
if( fee_asset->get_id() != asset_id_type() )
|
||||
{
|
||||
db().modify(*fee_asset_dyn_data, [this](asset_dynamic_data_object& d) {
|
||||
d.accumulated_fees += fee_from_account.amount;
|
||||
d.fee_pool -= core_fee_paid;
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void generic_evaluator::pay_fee()
|
||||
{ try {
|
||||
database& d = db();
|
||||
/// TODO: db().pay_fee( account_id, core_fee );
|
||||
d.modify(*fee_paying_account_statistics, [&](account_statistics_object& s)
|
||||
{
|
||||
s.pay_fee( core_fee_paid, d.get_global_properties().parameters.cashback_vesting_threshold );
|
||||
});
|
||||
if( !trx_state->skip_fee ) {
|
||||
database& d = db();
|
||||
/// TODO: db().pay_fee( account_id, core_fee );
|
||||
d.modify(*fee_paying_account_statistics, [&](account_statistics_object& s)
|
||||
{
|
||||
s.pay_fee( core_fee_paid, d.get_global_properties().parameters.cashback_vesting_threshold );
|
||||
});
|
||||
}
|
||||
} FC_CAPTURE_AND_RETHROW() }
|
||||
|
||||
} }
|
||||
|
|
|
|||
4
libraries/chain/hardfork.d/538.hf
Normal file
4
libraries/chain/hardfork.d/538.hf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// #538 Buyback accounts
|
||||
#ifndef HARDFORK_538_TIME
|
||||
#define HARDFORK_538_TIME (fc::time_point_sec( 1455127200 ))
|
||||
#endif
|
||||
4
libraries/chain/hardfork.d/555.hf
Normal file
4
libraries/chain/hardfork.d/555.hf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// #555 Buyback accounts
|
||||
#ifndef HARDFORK_555_TIME
|
||||
#define HARDFORK_555_TIME (fc::time_point_sec( 1455127200 ))
|
||||
#endif
|
||||
|
|
@ -208,6 +208,13 @@ namespace graphene { namespace chain {
|
|||
special_authority owner_special_authority = no_special_authority();
|
||||
special_authority active_special_authority = no_special_authority();
|
||||
|
||||
/**
|
||||
* This is a set of assets which the account is allowed to have.
|
||||
* This is utilized to restrict buyback accounts to the assets that trade in their markets.
|
||||
* In the future we may expand this to allow accounts to e.g. voluntarily restrict incoming transfers.
|
||||
*/
|
||||
optional< flat_set<asset_id_type> > allowed_assets;
|
||||
|
||||
bool has_special_authority()const
|
||||
{
|
||||
return (owner_special_authority.which() != special_authority::tag< no_special_authority >::value)
|
||||
|
|
@ -243,12 +250,6 @@ namespace graphene { namespace chain {
|
|||
return !is_basic_account(now);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this account is whitelisted and not blacklisted to transact in the provided asset; false
|
||||
* otherwise.
|
||||
*/
|
||||
bool is_authorized_asset(const asset_object& asset_obj, const database& d)const;
|
||||
|
||||
account_id_type get_id()const { return id; }
|
||||
};
|
||||
|
||||
|
|
@ -364,6 +365,7 @@ FC_REFLECT_DERIVED( graphene::chain::account_object,
|
|||
(whitelisted_accounts)(blacklisted_accounts)
|
||||
(cashback_vb)
|
||||
(owner_special_authority)(active_special_authority)
|
||||
(allowed_assets)
|
||||
)
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::account_balance_object,
|
||||
|
|
|
|||
|
|
@ -130,6 +130,8 @@ namespace graphene { namespace chain {
|
|||
/// Extra data associated with BitAssets. This field is non-null if and only if is_market_issued() returns true
|
||||
optional<asset_bitasset_data_id_type> bitasset_data_id;
|
||||
|
||||
optional<account_id_type> buyback_account;
|
||||
|
||||
asset_id_type get_id()const { return id; }
|
||||
|
||||
void validate()const
|
||||
|
|
@ -266,4 +268,5 @@ FC_REFLECT_DERIVED( graphene::chain::asset_object, (graphene::db::object),
|
|||
(options)
|
||||
(dynamic_asset_data_id)
|
||||
(bitasset_data_id)
|
||||
(buyback_account)
|
||||
)
|
||||
|
|
|
|||
34
libraries/chain/include/graphene/chain/buyback.hpp
Normal file
34
libraries/chain/include/graphene/chain/buyback.hpp
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., 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 <graphene/chain/protocol/buyback.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
class database;
|
||||
|
||||
void evaluate_buyback_account_options( const database& db, const buyback_account_options& auth );
|
||||
|
||||
} } // graphene::chain
|
||||
67
libraries/chain/include/graphene/chain/buyback_object.hpp
Normal file
67
libraries/chain/include/graphene/chain/buyback_object.hpp
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., 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 <graphene/chain/protocol/types.hpp>
|
||||
#include <graphene/db/object.hpp>
|
||||
#include <graphene/db/generic_index.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
/**
|
||||
* buyback_authority_object only exists to help with a specific indexing problem.
|
||||
* We want to be able to iterate over all assets that have a buyback program.
|
||||
* However, assets which have a buyback program are very rare. So rather
|
||||
* than indexing asset_object by the buyback field (requiring additional
|
||||
* bookkeeping for every asset), we instead maintain a buyback_object
|
||||
* pointing to each asset which has buyback (requiring additional
|
||||
* bookkeeping only for every asset which has buyback).
|
||||
*
|
||||
* This class is an implementation detail.
|
||||
*/
|
||||
|
||||
class buyback_object : public graphene::db::abstract_object< buyback_object >
|
||||
{
|
||||
public:
|
||||
static const uint8_t space_id = implementation_ids;
|
||||
static const uint8_t type_id = impl_buyback_object_type;
|
||||
|
||||
asset_id_type asset_to_buy;
|
||||
};
|
||||
|
||||
struct by_asset;
|
||||
|
||||
typedef multi_index_container<
|
||||
buyback_object,
|
||||
indexed_by<
|
||||
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
|
||||
ordered_unique< tag<by_asset>, member< buyback_object, asset_id_type, &buyback_object::asset_to_buy> >
|
||||
>
|
||||
> buyback_multi_index_type;
|
||||
|
||||
typedef generic_index< buyback_object, buyback_multi_index_type > buyback_index;
|
||||
|
||||
} } // graphene::chain
|
||||
|
||||
FC_REFLECT( graphene::chain::buyback_object, (asset_to_buy) )
|
||||
|
|
@ -107,6 +107,7 @@
|
|||
#define GRAPHENE_DEFAULT_FEE_LIQUIDATION_THRESHOLD GRAPHENE_BLOCKCHAIN_PRECISION * 100;
|
||||
#define GRAPHENE_DEFAULT_ACCOUNTS_PER_FEE_SCALE 1000
|
||||
#define GRAPHENE_DEFAULT_ACCOUNT_FEE_SCALE_BITSHIFTS 4
|
||||
#define GRAPHENE_DEFAULT_MAX_BUYBACK_MARKETS 4
|
||||
|
||||
#define GRAPHENE_MAX_WORKER_NAME_LENGTH 63
|
||||
|
||||
|
|
|
|||
|
|
@ -370,7 +370,7 @@ namespace graphene { namespace chain {
|
|||
/**
|
||||
* @return true if the order was completely filled and thus freed.
|
||||
*/
|
||||
bool fill_order( const limit_order_object& order, const asset& pays, const asset& receives );
|
||||
bool fill_order( const limit_order_object& order, const asset& pays, const asset& receives, bool cull_if_small );
|
||||
bool fill_order( const call_order_object& order, const asset& pays, const asset& receives );
|
||||
bool fill_order( const force_settlement_object& settle, const asset& pays, const asset& receives );
|
||||
|
||||
|
|
@ -412,12 +412,14 @@ namespace graphene { namespace chain {
|
|||
|
||||
//////////////////// db_block.cpp ////////////////////
|
||||
|
||||
public:
|
||||
// these were formerly private, but they have a fairly well-defined API, so let's make them public
|
||||
void apply_block( const signed_block& next_block, uint32_t skip = skip_nothing );
|
||||
processed_transaction apply_transaction( const signed_transaction& trx, uint32_t skip = skip_nothing );
|
||||
operation_result apply_operation( transaction_evaluation_state& eval_state, const operation& op );
|
||||
private:
|
||||
void _apply_block( const signed_block& next_block );
|
||||
processed_transaction _apply_transaction( const signed_transaction& trx );
|
||||
operation_result apply_operation( transaction_evaluation_state& eval_state, const operation& op );
|
||||
|
||||
|
||||
///Steps involved in applying a new block
|
||||
///@{
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
*/
|
||||
#pragma once
|
||||
#include <graphene/chain/exceptions.hpp>
|
||||
#include <graphene/chain/transaction_evaluation_state.hpp>
|
||||
#include <graphene/chain/protocol/operations.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
|
@ -227,10 +228,13 @@ namespace graphene { namespace chain {
|
|||
const auto& op = o.get<typename DerivedEvaluator::operation_type>();
|
||||
|
||||
prepare_fee(op.fee_payer(), op.fee);
|
||||
GRAPHENE_ASSERT( core_fee_paid >= db().current_fee_schedule().calculate_fee( op ).amount,
|
||||
insufficient_fee,
|
||||
"Insufficient Fee Paid",
|
||||
("core_fee_paid",core_fee_paid)("required",db().current_fee_schedule().calculate_fee( op ).amount) );
|
||||
if( !trx_state->skip_fee_schedule_check )
|
||||
{
|
||||
GRAPHENE_ASSERT( core_fee_paid >= db().current_fee_schedule().calculate_fee( op ).amount,
|
||||
insufficient_fee,
|
||||
"Insufficient Fee Paid",
|
||||
("core_fee_paid",core_fee_paid)("required",db().current_fee_schedule().calculate_fee( op ).amount) );
|
||||
}
|
||||
|
||||
return eval->do_evaluate(op);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,6 +104,9 @@ namespace graphene { namespace chain {
|
|||
GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( account_create );
|
||||
GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( max_auth_exceeded, account_create, 1, "Exceeds max authority fan-out" )
|
||||
GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( auth_account_not_found, account_create, 2, "Auth account not found" )
|
||||
GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( buyback_incorrect_issuer, account_create, 3, "Incorrect issuer specified for account" )
|
||||
GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( buyback_already_exists, account_create, 4, "Cannot create buyback for asset which already has buyback" )
|
||||
GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( buyback_too_many_markets, account_create, 5, "Too many buyback markets" )
|
||||
|
||||
GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( account_update );
|
||||
GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( max_auth_exceeded, account_update, 1, "Exceeds max authority fan-out" )
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., 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
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
class account_object;
|
||||
class asset_object;
|
||||
class database;
|
||||
|
||||
namespace detail {
|
||||
|
||||
bool _is_authorized_asset(const database& d, const account_object& acct, const asset_object& asset_obj);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the account is whitelisted and not blacklisted to transact in the provided asset; false
|
||||
* otherwise.
|
||||
*/
|
||||
|
||||
inline bool is_authorized_asset(const database& d, const account_object& acct, const asset_object& asset_obj)
|
||||
{
|
||||
bool fast_check = !(asset_obj.options.flags & white_list);
|
||||
fast_check &= !(acct.allowed_assets.valid());
|
||||
|
||||
if( fast_check )
|
||||
return true;
|
||||
|
||||
bool slow_check = detail::_is_authorized_asset( d, acct, asset_obj );
|
||||
return slow_check;
|
||||
}
|
||||
|
||||
} }
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
*/
|
||||
#pragma once
|
||||
#include <graphene/chain/protocol/base.hpp>
|
||||
#include <graphene/chain/protocol/buyback.hpp>
|
||||
#include <graphene/chain/protocol/ext.hpp>
|
||||
#include <graphene/chain/protocol/special_authority.hpp>
|
||||
#include <graphene/chain/protocol/types.hpp>
|
||||
|
|
@ -69,6 +70,7 @@ namespace graphene { namespace chain {
|
|||
optional< void_t > null_ext;
|
||||
optional< special_authority > owner_special_authority;
|
||||
optional< special_authority > active_special_authority;
|
||||
optional< buyback_account_options > buyback_options;
|
||||
};
|
||||
|
||||
struct fee_parameters_type
|
||||
|
|
@ -98,6 +100,14 @@ namespace graphene { namespace chain {
|
|||
account_id_type fee_payer()const { return registrar; }
|
||||
void validate()const;
|
||||
share_type calculate_fee(const fee_parameters_type& )const;
|
||||
|
||||
void get_required_active_authorities( flat_set<account_id_type>& a )const
|
||||
{
|
||||
// registrar should be required anyway as it is the fee_payer(), but we insert it here just to be sure
|
||||
a.insert( registrar );
|
||||
if( extensions.value.buyback_options.valid() )
|
||||
a.insert( extensions.value.buyback_options->asset_to_buy_issuer );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -258,7 +268,7 @@ FC_REFLECT_TYPENAME( graphene::chain::account_whitelist_operation::account_listi
|
|||
FC_REFLECT_ENUM( graphene::chain::account_whitelist_operation::account_listing,
|
||||
(no_listing)(white_listed)(black_listed)(white_and_black_listed))
|
||||
|
||||
FC_REFLECT(graphene::chain::account_create_operation::ext, (null_ext)(owner_special_authority)(active_special_authority) )
|
||||
FC_REFLECT(graphene::chain::account_create_operation::ext, (null_ext)(owner_special_authority)(active_special_authority)(buyback_options) )
|
||||
FC_REFLECT( graphene::chain::account_create_operation,
|
||||
(fee)(registrar)
|
||||
(referrer)(referrer_percent)
|
||||
|
|
|
|||
|
|
@ -109,6 +109,11 @@ namespace graphene { namespace chain {
|
|||
uint32_t num_auths()const { return account_auths.size() + key_auths.size() + address_auths.size(); }
|
||||
void clear() { account_auths.clear(); key_auths.clear(); }
|
||||
|
||||
static authority null_authority()
|
||||
{
|
||||
return authority( 1, GRAPHENE_NULL_ACCOUNT, 1 );
|
||||
}
|
||||
|
||||
uint32_t weight_threshold = 0;
|
||||
flat_map<account_id_type,weight_type> account_auths;
|
||||
flat_map<public_key_type,weight_type> key_auths;
|
||||
|
|
|
|||
52
libraries/chain/include/graphene/chain/protocol/buyback.hpp
Normal file
52
libraries/chain/include/graphene/chain/protocol/buyback.hpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., 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 <graphene/chain/protocol/types.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
struct buyback_account_options
|
||||
{
|
||||
/**
|
||||
* The asset to buy.
|
||||
*/
|
||||
asset_id_type asset_to_buy;
|
||||
|
||||
/**
|
||||
* Issuer of the asset. Must sign the transaction, must match issuer
|
||||
* of specified asset.
|
||||
*/
|
||||
account_id_type asset_to_buy_issuer;
|
||||
|
||||
/**
|
||||
* What assets the account is willing to buy with.
|
||||
* Other assets will just sit there since the account has null authority.
|
||||
*/
|
||||
flat_set< asset_id_type > markets;
|
||||
};
|
||||
|
||||
} }
|
||||
|
||||
FC_REFLECT( graphene::chain::buyback_account_options, (asset_to_buy)(asset_to_buy_issuer)(markets) );
|
||||
|
|
@ -153,7 +153,8 @@ namespace graphene { namespace chain {
|
|||
impl_chain_property_object_type,
|
||||
impl_witness_schedule_object_type,
|
||||
impl_budget_record_object_type,
|
||||
impl_special_authority_object_type
|
||||
impl_special_authority_object_type,
|
||||
impl_buyback_object_type
|
||||
};
|
||||
|
||||
//typedef fc::unsigned_int object_id_type;
|
||||
|
|
@ -203,6 +204,7 @@ namespace graphene { namespace chain {
|
|||
class witness_schedule_object;
|
||||
class budget_record_object;
|
||||
class special_authority_object;
|
||||
class buyback_object;
|
||||
|
||||
typedef object_id< implementation_ids, impl_global_property_object_type, global_property_object> global_property_id_type;
|
||||
typedef object_id< implementation_ids, impl_dynamic_global_property_object_type, dynamic_global_property_object> dynamic_global_property_id_type;
|
||||
|
|
@ -221,6 +223,7 @@ namespace graphene { namespace chain {
|
|||
typedef object_id< implementation_ids, impl_budget_record_object_type, budget_record_object > budget_record_id_type;
|
||||
typedef object_id< implementation_ids, impl_blinded_balance_object_type, blinded_balance_object > blinded_balance_id_type;
|
||||
typedef object_id< implementation_ids, impl_special_authority_object_type, special_authority_object > special_authority_id_type;
|
||||
typedef object_id< implementation_ids, impl_buyback_object_type, buyback_object > buyback_id_type;
|
||||
|
||||
typedef fc::array<char, GRAPHENE_MAX_ASSET_SYMBOL_LENGTH> symbol_type;
|
||||
typedef fc::ripemd160 block_id_type;
|
||||
|
|
@ -351,6 +354,7 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type,
|
|||
(impl_witness_schedule_object_type)
|
||||
(impl_budget_record_object_type)
|
||||
(impl_special_authority_object_type)
|
||||
(impl_buyback_object_type)
|
||||
)
|
||||
|
||||
FC_REFLECT_TYPENAME( graphene::chain::share_type )
|
||||
|
|
|
|||
|
|
@ -45,5 +45,7 @@ namespace graphene { namespace chain {
|
|||
const signed_transaction* _trx = nullptr;
|
||||
database* _db = nullptr;
|
||||
bool _is_proposed_trx = false;
|
||||
bool skip_fee = false;
|
||||
bool skip_fee_schedule_check = false;
|
||||
};
|
||||
} } // namespace graphene::chain
|
||||
|
|
|
|||
70
libraries/chain/is_authorized_asset.cpp
Normal file
70
libraries/chain/is_authorized_asset.cpp
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
*
|
||||
* The MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <graphene/chain/account_object.hpp>
|
||||
#include <graphene/chain/asset_object.hpp>
|
||||
|
||||
#include <graphene/chain/database.hpp>
|
||||
#include <graphene/chain/hardfork.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
namespace detail {
|
||||
|
||||
bool _is_authorized_asset(
|
||||
const database& d,
|
||||
const account_object& acct,
|
||||
const asset_object& asset_obj)
|
||||
{
|
||||
if( acct.allowed_assets.valid() )
|
||||
{
|
||||
if( acct.allowed_assets->find( asset_obj.id ) == acct.allowed_assets->end() )
|
||||
return false;
|
||||
// must still pass other checks even if it is in allowed_assets
|
||||
}
|
||||
|
||||
for( const auto id : acct.blacklisting_accounts )
|
||||
{
|
||||
if( asset_obj.options.blacklist_authorities.find(id) != asset_obj.options.blacklist_authorities.end() )
|
||||
return false;
|
||||
}
|
||||
|
||||
if( d.head_block_time() > HARDFORK_415_TIME )
|
||||
{
|
||||
if( asset_obj.options.whitelist_authorities.size() == 0 )
|
||||
return true;
|
||||
}
|
||||
|
||||
for( const auto id : acct.whitelisting_accounts )
|
||||
{
|
||||
if( asset_obj.options.whitelist_authorities.find(id) != asset_obj.options.whitelist_authorities.end() )
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
} } // graphene::chain
|
||||
|
|
@ -23,11 +23,14 @@
|
|||
*/
|
||||
#include <graphene/chain/account_object.hpp>
|
||||
#include <graphene/chain/asset_object.hpp>
|
||||
#include <graphene/chain/market_object.hpp>
|
||||
|
||||
#include <graphene/chain/market_evaluator.hpp>
|
||||
|
||||
#include <graphene/chain/database.hpp>
|
||||
#include <graphene/chain/exceptions.hpp>
|
||||
#include <graphene/chain/hardfork.hpp>
|
||||
#include <graphene/chain/market_evaluator.hpp>
|
||||
#include <graphene/chain/market_object.hpp>
|
||||
#include <graphene/chain/is_authorized_asset.hpp>
|
||||
|
||||
#include <graphene/chain/protocol/market.hpp>
|
||||
|
||||
|
|
@ -49,16 +52,8 @@ void_result limit_order_create_evaluator::do_evaluate(const limit_order_create_o
|
|||
if( _sell_asset->options.blacklist_markets.size() )
|
||||
FC_ASSERT( _sell_asset->options.blacklist_markets.find(_receive_asset->id) == _sell_asset->options.blacklist_markets.end() );
|
||||
|
||||
if( d.head_block_time() <= HARDFORK_416_TIME )
|
||||
{
|
||||
if( _sell_asset->options.flags & white_list ) FC_ASSERT( _seller->is_authorized_asset( *_sell_asset, d ) );
|
||||
if( _receive_asset->options.flags & white_list ) FC_ASSERT( _seller->is_authorized_asset( *_receive_asset, d ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
FC_ASSERT( _seller->is_authorized_asset( *_sell_asset, d ) );
|
||||
FC_ASSERT( _seller->is_authorized_asset( *_receive_asset, d ) );
|
||||
}
|
||||
FC_ASSERT( is_authorized_asset( d, *_seller, *_sell_asset ) );
|
||||
FC_ASSERT( is_authorized_asset( d, *_seller, *_receive_asset ) );
|
||||
|
||||
FC_ASSERT( d.get_balance( *_seller, *_sell_asset ) >= op.amount_to_sell, "insufficient balance",
|
||||
("balance",d.get_balance(*_seller,*_sell_asset))("amount_to_sell",op.amount_to_sell) );
|
||||
|
|
|
|||
|
|
@ -190,6 +190,19 @@ void account_create_operation::validate()const
|
|||
validate_special_authority( *extensions.value.owner_special_authority );
|
||||
if( extensions.value.active_special_authority.valid() )
|
||||
validate_special_authority( *extensions.value.active_special_authority );
|
||||
if( extensions.value.buyback_options.valid() )
|
||||
{
|
||||
FC_ASSERT( !(extensions.value.owner_special_authority.valid()) );
|
||||
FC_ASSERT( !(extensions.value.active_special_authority.valid()) );
|
||||
FC_ASSERT( owner == authority::null_authority() );
|
||||
FC_ASSERT( active == authority::null_authority() );
|
||||
size_t n_markets = extensions.value.buyback_options->markets.size();
|
||||
FC_ASSERT( n_markets > 0 );
|
||||
for( const asset_id_type m : extensions.value.buyback_options->markets )
|
||||
{
|
||||
FC_ASSERT( m != extensions.value.buyback_options->asset_to_buy );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include <graphene/chain/account_object.hpp>
|
||||
#include <graphene/chain/exceptions.hpp>
|
||||
#include <graphene/chain/hardfork.hpp>
|
||||
#include <graphene/chain/is_authorized_asset.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
void_result transfer_evaluator::do_evaluate( const transfer_operation& op )
|
||||
|
|
@ -35,34 +36,23 @@ void_result transfer_evaluator::do_evaluate( const transfer_operation& op )
|
|||
const account_object& from_account = op.from(d);
|
||||
const account_object& to_account = op.to(d);
|
||||
const asset_object& asset_type = op.amount.asset_id(d);
|
||||
const asset_object& fee_asset_type = op.fee.asset_id(d);
|
||||
|
||||
try {
|
||||
|
||||
if( asset_type.options.flags & white_list )
|
||||
{
|
||||
GRAPHENE_ASSERT(
|
||||
from_account.is_authorized_asset( asset_type, d ),
|
||||
transfer_from_account_not_whitelisted,
|
||||
"'from' account ${from} is not whitelisted for asset ${asset}",
|
||||
("from",op.from)
|
||||
("asset",op.amount.asset_id)
|
||||
);
|
||||
GRAPHENE_ASSERT(
|
||||
to_account.is_authorized_asset( asset_type, d ),
|
||||
transfer_to_account_not_whitelisted,
|
||||
"'to' account ${to} is not whitelisted for asset ${asset}",
|
||||
("to",op.to)
|
||||
("asset",op.amount.asset_id)
|
||||
);
|
||||
}
|
||||
|
||||
if( d.head_block_time() <= HARDFORK_419_TIME )
|
||||
{
|
||||
if( fee_asset_type.options.flags & white_list )
|
||||
FC_ASSERT( from_account.is_authorized_asset( asset_type, d ) );
|
||||
}
|
||||
// the above becomes no-op after hardfork because this check will then be performed in evaluator
|
||||
GRAPHENE_ASSERT(
|
||||
is_authorized_asset( d, from_account, asset_type ),
|
||||
transfer_from_account_not_whitelisted,
|
||||
"'from' account ${from} is not whitelisted for asset ${asset}",
|
||||
("from",op.from)
|
||||
("asset",op.amount.asset_id)
|
||||
);
|
||||
GRAPHENE_ASSERT(
|
||||
is_authorized_asset( d, to_account, asset_type ),
|
||||
transfer_to_account_not_whitelisted,
|
||||
"'to' account ${to} is not whitelisted for asset ${asset}",
|
||||
("to",op.to)
|
||||
("asset",op.amount.asset_id)
|
||||
);
|
||||
|
||||
if( asset_type.is_transfer_restricted() )
|
||||
{
|
||||
|
|
@ -108,18 +98,13 @@ void_result override_transfer_evaluator::do_evaluate( const override_transfer_op
|
|||
|
||||
const account_object& from_account = op.from(d);
|
||||
const account_object& to_account = op.to(d);
|
||||
const asset_object& fee_asset_type = op.fee.asset_id(d);
|
||||
|
||||
if( asset_type.options.flags & white_list )
|
||||
{
|
||||
FC_ASSERT( to_account.is_authorized_asset( asset_type, d ) );
|
||||
FC_ASSERT( from_account.is_authorized_asset( asset_type, d ) );
|
||||
}
|
||||
FC_ASSERT( is_authorized_asset( d, to_account, asset_type ) );
|
||||
FC_ASSERT( is_authorized_asset( d, from_account, asset_type ) );
|
||||
|
||||
if( d.head_block_time() <= HARDFORK_419_TIME )
|
||||
{
|
||||
if( fee_asset_type.options.flags & white_list )
|
||||
FC_ASSERT( from_account.is_authorized_asset( asset_type, d ) );
|
||||
FC_ASSERT( is_authorized_asset( d, from_account, asset_type ) );
|
||||
}
|
||||
// the above becomes no-op after hardfork because this check will then be performed in evaluator
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#include <graphene/chain/database.hpp>
|
||||
#include <graphene/chain/exceptions.hpp>
|
||||
#include <graphene/chain/hardfork.hpp>
|
||||
#include <graphene/chain/is_authorized_asset.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
|
|
@ -69,23 +70,10 @@ void_result withdraw_permission_claim_evaluator::do_evaluate(const withdraw_perm
|
|||
const asset_object& _asset = op.amount_to_withdraw.asset_id(d);
|
||||
if( _asset.is_transfer_restricted() ) FC_ASSERT( _asset.issuer == permit.authorized_account || _asset.issuer == permit.withdraw_from_account );
|
||||
|
||||
if( d.head_block_time() <= HARDFORK_416_TIME )
|
||||
{
|
||||
if( _asset.options.flags & white_list )
|
||||
{
|
||||
const account_object& from = op.withdraw_to_account(d);
|
||||
const account_object& to = permit.authorized_account(d);
|
||||
FC_ASSERT( to.is_authorized_asset( _asset, d ) );
|
||||
FC_ASSERT( from.is_authorized_asset( _asset, d ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const account_object& from = op.withdraw_to_account(d);
|
||||
const account_object& to = permit.authorized_account(d);
|
||||
FC_ASSERT( to.is_authorized_asset( _asset, d ) );
|
||||
FC_ASSERT( from.is_authorized_asset( _asset, d ) );
|
||||
}
|
||||
const account_object& from = op.withdraw_to_account(d);
|
||||
const account_object& to = permit.authorized_account(d);
|
||||
FC_ASSERT( is_authorized_asset( d, to, _asset ) );
|
||||
FC_ASSERT( is_authorized_asset( d, from, _asset ) );
|
||||
|
||||
return void_result();
|
||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||
|
|
|
|||
|
|
@ -1483,4 +1483,141 @@ BOOST_AUTO_TEST_CASE( top_n_special )
|
|||
} FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( buyback )
|
||||
{
|
||||
ACTORS( (alice)(bob)(chloe)(dan)(izzy)(philbin) );
|
||||
upgrade_to_lifetime_member(philbin_id);
|
||||
|
||||
generate_blocks( HARDFORK_538_TIME );
|
||||
generate_blocks( HARDFORK_555_TIME );
|
||||
|
||||
try
|
||||
{
|
||||
{
|
||||
//
|
||||
// Izzy (issuer)
|
||||
// Alice, Bob, Chloe, Dan (ABCD)
|
||||
// Rex (recycler -- buyback account)
|
||||
// Philbin (registrar)
|
||||
//
|
||||
|
||||
asset_id_type nono_id = create_user_issued_asset( "NONO", izzy_id(db), 0 ).id;
|
||||
asset_id_type buyme_id = create_user_issued_asset( "BUYME", izzy_id(db), 0 ).id;
|
||||
|
||||
// Create a buyback account
|
||||
account_id_type rex_id;
|
||||
{
|
||||
buyback_account_options bbo;
|
||||
bbo.asset_to_buy = buyme_id;
|
||||
bbo.asset_to_buy_issuer = izzy_id;
|
||||
bbo.markets.emplace( asset_id_type() );
|
||||
account_create_operation create_op = make_account( "rex" );
|
||||
create_op.registrar = philbin_id;
|
||||
create_op.extensions.value.buyback_options = bbo;
|
||||
create_op.owner = authority::null_authority();
|
||||
create_op.active = authority::null_authority();
|
||||
|
||||
// Let's break it...
|
||||
|
||||
signed_transaction tx;
|
||||
tx.operations.push_back( create_op );
|
||||
set_expiration( db, tx );
|
||||
|
||||
tx.operations.back().get< account_create_operation >().extensions.value.buyback_options->asset_to_buy_issuer = alice_id;
|
||||
sign( tx, alice_private_key );
|
||||
sign( tx, philbin_private_key );
|
||||
|
||||
// Alice and Philbin signed, but asset issuer is invalid
|
||||
GRAPHENE_CHECK_THROW( db.push_transaction(tx), account_create_buyback_incorrect_issuer );
|
||||
|
||||
tx.signatures.clear();
|
||||
tx.operations.back().get< account_create_operation >().extensions.value.buyback_options->asset_to_buy_issuer = izzy_id;
|
||||
sign( tx, philbin_private_key );
|
||||
|
||||
// Izzy didn't sign
|
||||
GRAPHENE_CHECK_THROW( db.push_transaction(tx), tx_missing_active_auth );
|
||||
sign( tx, izzy_private_key );
|
||||
|
||||
// OK
|
||||
processed_transaction ptx = db.push_transaction( tx );
|
||||
rex_id = ptx.operation_results.back().get< object_id_type >();
|
||||
|
||||
// Try to create another account rex2 which is bbo on same asset
|
||||
tx.signatures.clear();
|
||||
tx.operations.back().get< account_create_operation >().name = "rex2";
|
||||
sign( tx, izzy_private_key );
|
||||
sign( tx, philbin_private_key );
|
||||
GRAPHENE_CHECK_THROW( db.push_transaction(tx), account_create_buyback_already_exists );
|
||||
}
|
||||
|
||||
// issue some BUYME to Alice
|
||||
// we need to set_expiration() before issue_uia() because the latter doens't call it #11
|
||||
set_expiration( db, trx ); // #11
|
||||
issue_uia( alice_id, asset( 1000, buyme_id ) );
|
||||
issue_uia( alice_id, asset( 1000, nono_id ) );
|
||||
|
||||
// Alice wants to sell 100 BUYME for 1000 BTS, a middle price.
|
||||
limit_order_id_type order_id_mid = create_sell_order( alice_id, asset( 100, buyme_id ), asset( 1000, asset_id_type() ) )->id;
|
||||
|
||||
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||
generate_block();
|
||||
|
||||
// no success because buyback has none for sale
|
||||
BOOST_CHECK( order_id_mid(db).for_sale == 100 );
|
||||
|
||||
// but we can send some to buyback
|
||||
fund( rex_id(db), asset( 100, asset_id_type() ) );
|
||||
// no action until next maint
|
||||
BOOST_CHECK( order_id_mid(db).for_sale == 100 );
|
||||
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||
generate_block();
|
||||
|
||||
// partial fill, Alice now sells 90 BUYME for 900 BTS.
|
||||
BOOST_CHECK( order_id_mid(db).for_sale == 90 );
|
||||
|
||||
// TODO check burn amount
|
||||
|
||||
// aagh more state in trx
|
||||
set_expiration( db, trx ); // #11
|
||||
|
||||
// Selling 10 BUYME for 50 BTS, a low price.
|
||||
limit_order_id_type order_id_low = create_sell_order( alice_id, asset( 10, buyme_id ), asset( 50, asset_id_type() ) )->id;
|
||||
// Selling 10 BUYME for 150 BTS, a high price.
|
||||
limit_order_id_type order_id_high = create_sell_order( alice_id, asset( 10, buyme_id ), asset( 150, asset_id_type() ) )->id;
|
||||
|
||||
fund( rex_id(db), asset( 250, asset_id_type() ) );
|
||||
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||
generate_block();
|
||||
|
||||
BOOST_CHECK( db.find( order_id_low ) == nullptr );
|
||||
BOOST_CHECK( db.find( order_id_mid ) != nullptr );
|
||||
BOOST_CHECK( db.find( order_id_high ) != nullptr );
|
||||
|
||||
// 250 CORE in rex 90 BUYME in mid order 10 BUYME in low order
|
||||
// 50 CORE goes to low order, buy 10 for 50 CORE
|
||||
// 200 CORE goes to mid order, buy 20 for 200 CORE
|
||||
// 70 BUYME in mid order 0 BUYME in low order
|
||||
|
||||
idump( (order_id_mid(db)) );
|
||||
BOOST_CHECK( order_id_mid(db).for_sale == 70 );
|
||||
BOOST_CHECK( order_id_high(db).for_sale == 10 );
|
||||
|
||||
BOOST_CHECK( get_balance( rex_id, asset_id_type() ) == 0 );
|
||||
|
||||
// clear out the books -- 700 left on mid order, 150 left on high order, so 2000 BTS should result in 1150 left over
|
||||
|
||||
fund( rex_id(db), asset( 2000, asset_id_type() ) );
|
||||
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||
|
||||
idump( (get_balance( rex_id, asset_id_type() )) );
|
||||
|
||||
BOOST_CHECK( get_balance( rex_id, asset_id_type() ) == 1150 );
|
||||
|
||||
GRAPHENE_CHECK_THROW( transfer( alice_id, rex_id, asset( 1, nono_id ) ), fc::exception );
|
||||
// TODO: Check cancellation works for account which is BTS-restricted
|
||||
}
|
||||
|
||||
} FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#include <graphene/chain/database.hpp>
|
||||
#include <graphene/chain/exceptions.hpp>
|
||||
#include <graphene/chain/hardfork.hpp>
|
||||
#include <graphene/chain/is_authorized_asset.hpp>
|
||||
|
||||
#include <graphene/chain/account_object.hpp>
|
||||
#include <graphene/chain/asset_object.hpp>
|
||||
|
|
@ -161,7 +162,7 @@ BOOST_AUTO_TEST_CASE( issue_whitelist_uia )
|
|||
}
|
||||
PUSH_TX( db, trx, ~0 );
|
||||
|
||||
BOOST_CHECK(nathan_id(db).is_authorized_asset(uia_id(db), db));
|
||||
BOOST_CHECK(is_authorized_asset( db, nathan_id(db), uia_id(db) ));
|
||||
BOOST_CHECK_EQUAL(get_balance(nathan_id, uia_id), 1000);
|
||||
|
||||
// Make a whitelist, now it should fail
|
||||
|
|
@ -265,7 +266,7 @@ BOOST_AUTO_TEST_CASE( transfer_whitelist_uia )
|
|||
wop.account_to_list = nathan.id;
|
||||
trx.operations.back() = wop;
|
||||
PUSH_TX( db, trx, ~0 );
|
||||
BOOST_CHECK( !(nathan.is_authorized_asset(advanced, db)) );
|
||||
BOOST_CHECK( !(is_authorized_asset( db, nathan, advanced )) );
|
||||
|
||||
BOOST_TEST_MESSAGE( "Attempting to transfer from nathan after blacklisting, should fail" );
|
||||
op.amount = advanced.amount(50);
|
||||
|
|
@ -323,7 +324,7 @@ BOOST_AUTO_TEST_CASE( transfer_whitelist_uia )
|
|||
|
||||
trx.operations.back() = op;
|
||||
//Fail because nathan is blacklisted
|
||||
BOOST_CHECK(!nathan.is_authorized_asset(advanced, db));
|
||||
BOOST_CHECK(!is_authorized_asset( db, nathan, advanced ));
|
||||
GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0 ), fc::exception);
|
||||
|
||||
//Remove nathan from committee's whitelist, add him to dan's. This should not authorize him to hold ADVANCED.
|
||||
|
|
@ -340,7 +341,7 @@ BOOST_AUTO_TEST_CASE( transfer_whitelist_uia )
|
|||
|
||||
trx.operations.back() = op;
|
||||
//Fail because nathan is not whitelisted
|
||||
BOOST_CHECK(!nathan.is_authorized_asset(advanced, db));
|
||||
BOOST_CHECK(!is_authorized_asset( db, nathan, advanced ));
|
||||
GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0 ), fc::exception);
|
||||
|
||||
burn.payer = dan.id;
|
||||
|
|
|
|||
Loading…
Reference in a new issue