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:
theoreticalbts 2016-02-11 13:03:19 -05:00
commit e46af9f372
35 changed files with 756 additions and 156 deletions

View file

@ -346,6 +346,8 @@ namespace graphene { namespace app {
break;
case impl_special_authority_object_type:
break;
case impl_buyback_object_type:
break;
}
}
return result;

View file

@ -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"
)

View file

@ -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)) }

View file

@ -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);

View file

@ -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 );

View 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) );
}
} }

View file

@ -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,

View file

@ -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)

View file

@ -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;

View file

@ -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

View file

@ -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()
{

View file

@ -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() }
} }

View file

@ -0,0 +1,4 @@
// #538 Buyback accounts
#ifndef HARDFORK_538_TIME
#define HARDFORK_538_TIME (fc::time_point_sec( 1455127200 ))
#endif

View file

@ -0,0 +1,4 @@
// #555 Buyback accounts
#ifndef HARDFORK_555_TIME
#define HARDFORK_555_TIME (fc::time_point_sec( 1455127200 ))
#endif

View file

@ -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,

View file

@ -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)
)

View 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

View 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) )

View file

@ -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

View file

@ -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
///@{

View file

@ -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);
}

View file

@ -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" )

View file

@ -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;
}
} }

View file

@ -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)

View file

@ -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;

View 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) );

View file

@ -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 )

View file

@ -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

View 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

View file

@ -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) );

View file

@ -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 );
}
}
}

View file

@ -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

View file

@ -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) ) }

View file

@ -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()

View file

@ -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;