diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 8f775bbd..592040c4 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -161,14 +161,21 @@ namespace detail { for( int i = 0; i < 10; ++i ) { auto name = "init"+fc::to_string(i); - initial_state.initial_accounts.emplace_back(name, nathan_key.get_public_key(), true); + initial_state.initial_accounts.emplace_back(name, + nathan_key.get_public_key(), + nathan_key.get_public_key(), + true); initial_state.initial_committee.push_back({name}); initial_state.initial_witnesses.push_back({name, nathan_key.get_public_key(), secret}); } - initial_state.initial_accounts.emplace_back("nathan", address(public_key_type(nathan_key.get_public_key())), 1); + initial_state.initial_accounts.emplace_back("nathan", nathan_key.get_public_key()); + initial_state.initial_balances.push_back({nathan_key.get_public_key(), + GRAPHENE_SYMBOL, + GRAPHENE_MAX_SHARE_SUPPLY}); if( _options->count("genesis-json") ) - initial_state = fc::json::from_file(_options->at("genesis-json").as()).as(); + initial_state = fc::json::from_file(_options->at("genesis-json").as()) + .as(); else dlog("Allocating all stake to ${key}", ("key", utilities::key_to_wif(nathan_key))); @@ -199,20 +206,9 @@ namespace detail { try { if( id.item_type == graphene::net::block_message_type ) - { - // for some reason, the contains() function called by is_known_block - // throws when the block is not present (instead of returning false) - try - { - return _chain_db->is_known_block( id.item_hash ); - } - catch (const fc::key_not_found_exception&) - { - return false; - } - } + return _chain_db->is_known_block(id.item_hash); else - return _chain_db->is_known_transaction( id.item_hash ); // is_known_transaction behaves normally + return _chain_db->is_known_transaction(id.item_hash); } FC_CAPTURE_AND_RETHROW( (id) ) } @@ -255,7 +251,7 @@ namespace detail { virtual std::vector get_item_ids(uint32_t item_type, const std::vector& blockchain_synopsis, uint32_t& remaining_item_count, - uint32_t limit ) override + uint32_t limit) override { try { FC_ASSERT( item_type == graphene::net::block_message_type ); vector result; @@ -268,7 +264,7 @@ namespace detail { auto itr = blockchain_synopsis.rbegin(); while( itr != blockchain_synopsis.rend() ) { - if( _chain_db->is_known_block( *itr ) || *itr == block_id_type() ) + if( _chain_db->is_known_block(*itr) || *itr == block_id_type() ) { last_known_block_id = *itr; break; @@ -285,7 +281,6 @@ namespace detail { if( block_header::num_from_id(result.back()) < _chain_db->head_block_num() ) remaining_item_count = _chain_db->head_block_num() - block_header::num_from_id(result.back()); - idump((blockchain_synopsis)(limit)(result)(remaining_item_count)); return result; } FC_CAPTURE_AND_RETHROW( (blockchain_synopsis)(remaining_item_count)(limit) ) } diff --git a/libraries/chain/block_database.cpp b/libraries/chain/block_database.cpp index 434c6682..e4b4b01a 100644 --- a/libraries/chain/block_database.cpp +++ b/libraries/chain/block_database.cpp @@ -48,13 +48,11 @@ void block_database::open( const fc::path& dbdir ) } } FC_CAPTURE_AND_RETHROW( (dbdir) ) } - bool block_database::is_open()const { return _blocks.is_open(); } - void block_database::close() { _blocks.close(); @@ -67,7 +65,6 @@ void block_database::flush() _block_num_to_pos.flush(); } - void block_database::store( const block_id_type& id, const signed_block& b ) { auto num = block_header::num_from_id(id); @@ -82,7 +79,6 @@ void block_database::store( const block_id_type& id, const signed_block& b ) _block_num_to_pos.write( (char*)&e, sizeof(e) ); } - void block_database::remove( const block_id_type& id ) { try { index_entry e; @@ -102,25 +98,20 @@ void block_database::remove( const block_id_type& id ) } } FC_CAPTURE_AND_RETHROW( (id) ) } - - - - -bool block_database::contains( const block_id_type& id )const +bool block_database::contains( const block_id_type& id )const { index_entry e; auto index_pos = sizeof(e)*block_header::num_from_id(id); _block_num_to_pos.seekg( 0, _block_num_to_pos.end ); if ( _block_num_to_pos.tellg() <= index_pos ) - FC_THROW_EXCEPTION(fc::key_not_found_exception, "Block ${id} not contained in block database", ("id", id)); + return false; _block_num_to_pos.seekg( index_pos ); _block_num_to_pos.read( (char*)&e, sizeof(e) ); return e.block_id == id; } - -block_id_type block_database::fetch_block_id( uint32_t block_num )const +block_id_type block_database::fetch_block_id( uint32_t block_num )const { index_entry e; auto index_pos = sizeof(e)*block_num; @@ -134,22 +125,21 @@ block_id_type block_database::fetch_block_id( uint32_t block_num )const return e.block_id; } - optional block_database::fetch_optional( const block_id_type& id )const { - try + try { index_entry e; auto index_pos = sizeof(e)*block_header::num_from_id(id); _block_num_to_pos.seekg( 0, _block_num_to_pos.end ); if ( _block_num_to_pos.tellg() <= index_pos ) - FC_THROW_EXCEPTION(fc::key_not_found_exception, "Block ${id} not contained in block database", ("id", id)); - + return {}; + _block_num_to_pos.seekg( index_pos ); _block_num_to_pos.read( (char*)&e, sizeof(e) ); - + if( e.block_id != id ) return optional(); - + vector data( e.block_size ); _blocks.seekg( e.block_pos ); _blocks.read( data.data(), e.block_size ); @@ -168,13 +158,13 @@ optional block_database::fetch_optional( const block_id_type& id ) optional block_database::fetch_by_number( uint32_t block_num )const { - try + try { index_entry e; auto index_pos = sizeof(e)*block_num; _block_num_to_pos.seekg( 0, _block_num_to_pos.end ); if ( _block_num_to_pos.tellg() <= index_pos ) - FC_THROW_EXCEPTION(fc::key_not_found_exception, "Block number ${block_num} not contained in block database", ("block_num", block_num)); + return {}; _block_num_to_pos.seekg( index_pos, _block_num_to_pos.beg ); _block_num_to_pos.read( (char*)&e, sizeof(e) ); @@ -195,10 +185,9 @@ optional block_database::fetch_by_number( uint32_t block_num )cons return optional(); } - optional block_database::last()const { - try + try { index_entry e; _block_num_to_pos.seekg( 0, _block_num_to_pos.end ); @@ -231,5 +220,4 @@ optional block_database::last()const } return optional(); } - } } diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 2ae442c0..758ec444 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -301,10 +301,10 @@ signed_block database::_generate_block( { for( const auto& trx : tmp.transactions ) { - try { - push_transaction( trx, skip ); - } catch ( const fc::exception& e ) { - wlog( "Transaction is no longer valid: ${trx}", ("trx",trx) ); + try { + push_transaction( trx, skip ); + } catch ( const fc::exception& e ) { + wlog( "Transaction is no longer valid: ${trx}", ("trx",trx) ); } } return _generate_block( when, witness_id, block_signing_private_key ); @@ -435,28 +435,6 @@ processed_transaction database::apply_transaction( const signed_transaction& trx return result; } -struct signature_check_visitor -{ - typedef void result_type; - - flat_map& sigs; - signature_check_visitor( flat_map& s ):sigs(s){} - - template - result_type operator()( const T& o )const{} - - result_type operator()( const balance_claim_operation& o )const - { - for( auto& owner : o.owners ) - { - auto itr = sigs.find(owner); - FC_ASSERT( itr != sigs.end() ); - itr->second = true; - } - } - -}; - processed_transaction database::_apply_transaction( const signed_transaction& trx ) { try { uint32_t skip = get_node_properties().skip_flags; @@ -476,8 +454,6 @@ processed_transaction database::_apply_transaction( const signed_transaction& tr for( const auto& sig : trx.signatures ) FC_ASSERT( eval_state._sigs.insert( std::make_pair( address(fc::ecc::public_key( sig, trx.digest() )), false) ).second, "Multiple signatures by same key detected" ) ; - - trx.visit( signature_check_visitor(eval_state._sigs) ); } //If we're skipping tapos check, but not dupe check, assume all transactions have maximum expiration time. @@ -503,9 +479,9 @@ processed_transaction database::_apply_transaction( const signed_transaction& tr eval_state._sigs.reserve( trx.signatures.size() ); for( const auto& sig : trx.signatures ) - FC_ASSERT( eval_state._sigs.insert( std::make_pair(address(fc::ecc::public_key( sig, trx.digest(tapos_block_summary.block_id) )),false) ).second, "Multiple signatures by same key detected" ) ; - - trx.visit( signature_check_visitor(eval_state._sigs) ); + FC_ASSERT(eval_state._sigs.insert( + std::make_pair(address(fc::ecc::public_key(sig, trx.digest(tapos_block_summary.block_id) )), + false)).second, "Multiple signatures by same key detected"); } //Verify TaPoS block summary has correct ID prefix, and that this block's time is not past the expiration @@ -513,7 +489,7 @@ processed_transaction database::_apply_transaction( const signed_transaction& tr trx_expiration = tapos_block_summary.timestamp + chain_parameters.block_interval*trx.relative_expiration; } else if( trx.relative_expiration == 0 ) { trx_expiration = fc::time_point_sec() + fc::seconds(trx.ref_block_prefix); - FC_ASSERT( trx_expiration <= _pending_block.timestamp + chain_parameters.maximum_time_until_expiration, "", + FC_ASSERT( trx_expiration <= _pending_block.timestamp + chain_parameters.maximum_time_until_expiration, "", ("trx_expiration",trx_expiration)("_pending_block.timestamp",_pending_block.timestamp)("max_til_exp",chain_parameters.maximum_time_until_expiration)); } FC_ASSERT( _pending_block.timestamp <= trx_expiration, "", ("pending.timestamp",_pending_block.timestamp)("trx_exp",trx_expiration) ); diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 8c3bc627..a983bc87 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -161,7 +161,7 @@ void database::init_genesis(const genesis_state_type& genesis_state) transaction_evaluation_state genesis_eval_state(this); - // Create initial accounts + // Create blockchain accounts fc::ecc::private_key null_private_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("null_key"))); create( [&null_private_key](key_object& k) { k.key_data = public_key_type(null_private_key.get_public_key()); @@ -280,6 +280,8 @@ void database::init_genesis(const genesis_state_type& genesis_state) adjust_balance(GRAPHENE_COMMITTEE_ACCOUNT, -get_balance(GRAPHENE_COMMITTEE_ACCOUNT,{})); } + // TODO: Create initial vesting balances + // Create initial accounts if( !genesis_state.initial_accounts.empty() ) { @@ -288,12 +290,21 @@ void database::init_genesis(const genesis_state_type& genesis_state) key_id_type key_id = apply_operation(genesis_eval_state, key_create_operation({asset(), committee_account.id, - account.addr})).get(); + account.owner_key})).get(); account_create_operation cop; cop.name = account.name; cop.registrar = account_id_type(1); - cop.active = authority(1, key_id, 1); - cop.owner = cop.active; + cop.owner = authority(1, key_id, 1); + if( account.owner_key != account.active_key ) + { + key_id = apply_operation(genesis_eval_state, + key_create_operation({asset(), + committee_account.id, + account.owner_key})).get(); + cop.active = authority(1, key_id, 1); + } else { + cop.active = cop.owner; + } cop.options.memo_key = key_id; account_id_type account_id(apply_operation(genesis_eval_state, cop).get()); diff --git a/libraries/chain/evaluator.cpp b/libraries/chain/evaluator.cpp index b1852370..a1a2353c 100644 --- a/libraries/chain/evaluator.cpp +++ b/libraries/chain/evaluator.cpp @@ -28,7 +28,8 @@ #include namespace graphene { namespace chain { - database& generic_evaluator::db()const { return trx_state->db(); } +database& generic_evaluator::db()const { return trx_state->db(); } + operation_result generic_evaluator::start_evaluate( transaction_evaluation_state& eval_state, const operation& op, bool apply ) { try { trx_state = &eval_state; diff --git a/libraries/chain/include/graphene/chain/balance_evaluator.hpp b/libraries/chain/include/graphene/chain/balance_evaluator.hpp index 237b8d2e..3a96cde0 100644 --- a/libraries/chain/include/graphene/chain/balance_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/balance_evaluator.hpp @@ -1,47 +1,60 @@ #pragma once + +#include +#include +#include +#include #include namespace graphene { namespace chain { - /** - * @ingroup operations - */ - class balance_claim_evaluator : public evaluator +class balance_claim_evaluator : public evaluator +{ +public: + typedef balance_claim_operation operation_type; + + const balance_object* balance = nullptr; + asset amount_withdrawn; + + asset do_evaluate(const balance_claim_operation& op) { - public: - typedef balance_claim_operation operation_type; + database& d = db(); + balance = &op.balance_to_claim(d); - void_result do_evaluate( const balance_claim_operation& op ) - { - return void_result(); - } + FC_ASSERT(trx_state->_sigs.count(balance->owner) == 1); + trx_state->_sigs[balance->owner] = true; + FC_ASSERT(op.total_claimed.asset_id == balance->asset_type()); - /** - * @note the fee is always 0 for this particular operation because once the - * balance is claimed it frees up memory and it cannot be used to spam the network - */ - void_result do_apply( const balance_claim_operation& op ) - { - const auto& bal_idx = db().get_index_type(); - const auto& by_owner_idx = bal_idx.indices().get(); + if( balance->vesting_policy.valid() ) { + FC_ASSERT(op.total_claimed.amount == 0); + return amount_withdrawn = balance->vesting_policy->get_allowed_withdraw({balance->balance, + d.head_block_time(), + {}}); + } - asset total(0, op.total_claimed.asset_id); - for( const auto& owner : op.owners ) - { - auto itr = by_owner_idx.find( boost::make_tuple( owner, total.asset_id ) ); - if( itr != by_owner_idx.end() ) - { - total += itr->balance; - db().remove( *itr ); - } - } + FC_ASSERT(op.total_claimed == balance->balance); + return amount_withdrawn = op.total_claimed; + } - FC_ASSERT( total == op.total_claimed, "", ("total",total)("op",op) ); + /** + * @note the fee is always 0 for this particular operation because once the + * balance is claimed it frees up memory and it cannot be used to spam the network + */ + asset do_apply(const balance_claim_operation& op) + { + database& d = db(); - db().adjust_balance( op.deposit_to_account, total ); + if( balance->vesting_policy.valid() && amount_withdrawn < balance->balance ) + d.modify(*balance, [&](balance_object& b) { + b.vesting_policy->on_withdraw({b.balance, d.head_block_time(), amount_withdrawn}); + b.balance -= amount_withdrawn; + }); + else + d.remove(*balance); - return void_result(); - } - }; + d.adjust_balance(op.deposit_to_account, amount_withdrawn); + return amount_withdrawn; + } +}; } } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/balance_object.hpp b/libraries/chain/include/graphene/chain/balance_object.hpp index 9bbe6913..bc692388 100644 --- a/libraries/chain/include/graphene/chain/balance_object.hpp +++ b/libraries/chain/include/graphene/chain/balance_object.hpp @@ -1,5 +1,7 @@ #pragma once +#include + namespace graphene { namespace chain { class balance_object : public abstract_object @@ -10,6 +12,7 @@ namespace graphene { namespace chain { address owner; asset balance; + optional vesting_policy; asset_id_type asset_type()const { return balance.asset_id; } }; @@ -36,4 +39,4 @@ namespace graphene { namespace chain { using balance_index = generic_index; } } -FC_REFLECT_DERIVED( graphene::chain::balance_object, (graphene::db::object), (owner)(balance) ) +FC_REFLECT_DERIVED( graphene::chain::balance_object, (graphene::db::object), (owner)(balance)(vesting_policy) ) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index b36f5e11..7209669b 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -40,20 +40,34 @@ namespace graphene { namespace chain { using graphene::db::object; struct genesis_state_type { - struct genesis_account_type { - genesis_account_type(const string& name = string(), - const address& addr = address(), - bool is_lifetime_member = false) - : name(name), addr(addr), is_lifetime_member(is_lifetime_member){} + struct initial_account_type { + initial_account_type(const string& name = string(), + const public_key_type& owner_key = public_key_type(), + const public_key_type& active_key = public_key_type(), + bool is_lifetime_member = false) + : name(name), + owner_key(owner_key), + active_key(active_key == public_key_type()? owner_key : active_key), + is_lifetime_member(is_lifetime_member) + {} string name; - address addr; + public_key_type owner_key; + public_key_type active_key; bool is_lifetime_member; }; - struct genesis_balance_type { + struct initial_balance_type { address owner; string asset_symbol; share_type amount; }; + struct initial_vesting_balance_type { + address owner; + string asset_symbol; + share_type amount; + time_point_sec vesting_start_date; + time_point_sec earliest_withdrawal_date; + time_point_sec vesting_end_date; + }; struct initial_witness_type { /// Must correspond to one of the allocation targets. string owner_name; @@ -66,8 +80,8 @@ namespace graphene { namespace chain { }; chain_parameters initial_parameters; - vector initial_accounts; - vector initial_balances; + vector initial_accounts; + vector initial_balances; vector initial_witnesses; vector initial_committee; }; @@ -513,9 +527,12 @@ namespace graphene { namespace chain { } } } -FC_REFLECT(graphene::chain::genesis_state_type::genesis_account_type, (name)(addr)(is_lifetime_member)) -FC_REFLECT(graphene::chain::genesis_state_type::genesis_balance_type, +FC_REFLECT(graphene::chain::genesis_state_type::initial_account_type, (name)(owner_key)(active_key)(is_lifetime_member)) +FC_REFLECT(graphene::chain::genesis_state_type::initial_balance_type, (owner)(asset_symbol)(amount)) +FC_REFLECT(graphene::chain::genesis_state_type::initial_vesting_balance_type, + (owner)(asset_symbol)(amount)(vesting_start_date)(earliest_withdrawal_date)(vesting_end_date)) FC_REFLECT(graphene::chain::genesis_state_type::initial_witness_type, (owner_name)(block_signing_key)(initial_secret)) FC_REFLECT(graphene::chain::genesis_state_type::initial_committee_member_type, (owner_name)) -FC_REFLECT(graphene::chain::genesis_state_type, (initial_parameters)(initial_accounts)(initial_balances)(initial_witnesses)(initial_committee)) +FC_REFLECT(graphene::chain::genesis_state_type, + (initial_parameters)(initial_accounts)(initial_balances)(initial_witnesses)(initial_committee)) diff --git a/libraries/chain/include/graphene/chain/evaluator.hpp b/libraries/chain/include/graphene/chain/evaluator.hpp index 4e3996d4..aebbe260 100644 --- a/libraries/chain/include/graphene/chain/evaluator.hpp +++ b/libraries/chain/include/graphene/chain/evaluator.hpp @@ -22,6 +22,7 @@ namespace graphene { namespace chain { class database; + class signed_transaction; class generic_evaluator; class transaction_evaluation_state; @@ -42,175 +43,175 @@ namespace graphene { namespace chain { */ class evaluation_observer { - public: - virtual ~evaluation_observer(){} + public: + virtual ~evaluation_observer(){} - virtual void pre_evaluate( - const transaction_evaluation_state& eval_state, - const operation& op, - bool apply, - generic_evaluator* ge ) {} + virtual void pre_evaluate(const transaction_evaluation_state& eval_state, + const operation& op, + bool apply, + generic_evaluator* ge) + {} - virtual void post_evaluate( - const transaction_evaluation_state& eval_state, - const operation& op, - bool apply, - generic_evaluator* ge, - const operation_result& result ) {} + virtual void post_evaluate(const transaction_evaluation_state& eval_state, + const operation& op, + bool apply, + generic_evaluator* ge, + const operation_result& result) + {} - virtual void evaluation_failed( - const transaction_evaluation_state& eval_state, - const operation& op, - bool apply, - generic_evaluator* ge, - const operation_result& result ) {} + virtual void evaluation_failed(const transaction_evaluation_state& eval_state, + const operation& op, + bool apply, + generic_evaluator* ge, + const operation_result& result) + {} }; class generic_evaluator { - public: - virtual ~generic_evaluator(){} + public: + virtual ~generic_evaluator(){} - virtual int get_type()const = 0; - virtual operation_result start_evaluate( transaction_evaluation_state& eval_state, const operation& op, bool apply ); + virtual int get_type()const = 0; + virtual operation_result start_evaluate(transaction_evaluation_state& eval_state, const operation& op, bool apply); - /** @note derived classes should ASSUME that the default validation that is - * indepenent of chain state should be performed by op.validate() and should - * not perform these extra checks. - */ - virtual operation_result evaluate( const operation& op ) = 0; - virtual operation_result apply( const operation& op ) = 0; + /** + * @note derived classes should ASSUME that the default validation that is + * indepenent of chain state should be performed by op.validate() and should + * not perform these extra checks. + */ + virtual operation_result evaluate(const operation& op) = 0; + virtual operation_result apply(const operation& op) = 0; - database& db()const; + database& db()const; - void check_required_authorities(const operation& op); + void check_required_authorities(const operation& op); protected: - /** - * @brief Fetch objects relevant to fee payer and set pointer members - * @param account_id Account which is paying the fee - * @param fee The fee being paid. May be in assets other than core. - * - * This method verifies that the fee is valid and sets the object pointer members and the fee fields. It should - * be called during do_evaluate. - */ - void prepare_fee(account_id_type account_id, asset fee); - /// Pays the fee and returns the number of CORE asset that were paid. - void pay_fee(); + /** + * @brief Fetch objects relevant to fee payer and set pointer members + * @param account_id Account which is paying the fee + * @param fee The fee being paid. May be in assets other than core. + * + * This method verifies that the fee is valid and sets the object pointer members and the fee fields. It should + * be called during do_evaluate. + */ + void prepare_fee(account_id_type account_id, asset fee); + /// Pays the fee and returns the number of CORE asset that were paid. + void pay_fee(); - bool verify_authority( const account_object&, authority::classification ); - //bool verify_signature( const key_object& ); + bool verify_authority(const account_object&, authority::classification); - object_id_type get_relative_id( object_id_type rel_id )const; + object_id_type get_relative_id( object_id_type rel_id )const; - void check_relative_ids(const authority& a)const; - authority resolve_relative_ids( const authority& a )const; + void check_relative_ids(const authority& a)const; + authority resolve_relative_ids( const authority& a )const; - asset fee_from_account; - share_type core_fee_paid; - const account_object* fee_paying_account = nullptr; - const account_statistics_object* fee_paying_account_statistics = nullptr; - const asset_object* fee_asset = nullptr; - const asset_dynamic_data_object* fee_asset_dyn_data = nullptr; - transaction_evaluation_state* trx_state; + asset fee_from_account; + share_type core_fee_paid; + const account_object* fee_paying_account = nullptr; + const account_statistics_object* fee_paying_account_statistics = nullptr; + const asset_object* fee_asset = nullptr; + const asset_dynamic_data_object* fee_asset_dyn_data = nullptr; + transaction_evaluation_state* trx_state; }; class op_evaluator { - public: - virtual ~op_evaluator(){} - virtual operation_result evaluate( transaction_evaluation_state& eval_state, const operation& op, bool apply ) = 0; + public: + virtual ~op_evaluator(){} + virtual operation_result evaluate(transaction_evaluation_state& eval_state, const operation& op, bool apply) = 0; - vector< evaluation_observer* > eval_observers; + vector eval_observers; }; template class op_evaluator_impl : public op_evaluator { - public: - virtual operation_result evaluate( transaction_evaluation_state& eval_state, const operation& op, bool apply = true ) override + public: + virtual operation_result evaluate(transaction_evaluation_state& eval_state, const operation& op, bool apply = true) override + { + // fc::exception from observers are suppressed. + // fc::exception from evaluation is deferred (re-thrown + // after all observers receive evaluation_failed) + + T eval; + optional< fc::exception > evaluation_exception; + size_t observer_count = 0; + operation_result result; + + for( const auto& obs : eval_observers ) { - // fc::exception from observers are suppressed. - // fc::exception from evaluation is deferred (re-thrown - // after all observers receive evaluation_failed) - - T eval; - optional< fc::exception > evaluation_exception; - size_t observer_count = 0; - operation_result result; - - for( const auto& obs : eval_observers ) - { - try - { - obs->pre_evaluate( eval_state, op, apply, &eval ); - } - catch( const fc::exception& e ) - { - elog( "suppressed exception in observer pre method:\n${e}", ( "e", e.to_detail_string() ) ); - } - observer_count++; - } - - try - { - result = eval.start_evaluate( eval_state, op, apply ); - } - catch( const fc::exception& e ) - { - evaluation_exception = e; - } - - while( observer_count > 0 ) - { - --observer_count; - const auto& obs = eval_observers[ observer_count ]; - try - { - if( !evaluation_exception.valid() ) - obs->post_evaluate( eval_state, op, apply, &eval, result ); - else - obs->evaluation_failed( eval_state, op, apply, &eval, result ); - } - catch( const fc::exception& e ) - { - elog( "suppressed exception in observer post method:\n${e}", ( "e", e.to_detail_string() ) ); - } - } - - if( evaluation_exception.valid() ) - throw *evaluation_exception; - return result; + try + { + obs->pre_evaluate(eval_state, op, apply, &eval); + } + catch( const fc::exception& e ) + { + elog( "suppressed exception in observer pre method:\n${e}", ( "e", e.to_detail_string() ) ); + } + observer_count++; } + + try + { + result = eval.start_evaluate(eval_state, op, apply); + } + catch( const fc::exception& e ) + { + evaluation_exception = e; + } + + while( observer_count > 0 ) + { + --observer_count; + const auto& obs = eval_observers[observer_count]; + try + { + if( !evaluation_exception.valid() ) + obs->post_evaluate(eval_state, op, apply, &eval, result); + else + obs->evaluation_failed(eval_state, op, apply, &eval, result); + } + catch( const fc::exception& e ) + { + elog( "suppressed exception in observer post method:\n${e}", ( "e", e.to_detail_string() ) ); + } + } + + if( evaluation_exception.valid() ) + throw *evaluation_exception; + return result; + } }; template class evaluator : public generic_evaluator { - public: - virtual int get_type()const override { return operation::tag::value; } + public: + virtual int get_type()const override { return operation::tag::value; } - virtual operation_result evaluate( const operation& o ) final override - { - auto* eval = static_cast(this); - const auto& op = o.get(); + virtual operation_result evaluate(const operation& o) final override + { + auto* eval = static_cast(this); + const auto& op = o.get(); - prepare_fee(op.fee_payer(), op.fee); - FC_ASSERT( core_fee_paid >= op.calculate_fee(db().current_fee_schedule()) ); + prepare_fee(op.fee_payer(), op.fee); + FC_ASSERT( core_fee_paid >= op.calculate_fee(db().current_fee_schedule()) ); - return eval->do_evaluate( op ); - } - virtual operation_result apply( const operation& o ) final override - { - auto* eval = static_cast(this); - const auto& op = o.get(); + return eval->do_evaluate(op); + } + virtual operation_result apply(const operation& o) final override + { + auto* eval = static_cast(this); + const auto& op = o.get(); - pay_fee(); + pay_fee(); - auto result = eval->do_apply( op ); + auto result = eval->do_apply(op); - db().adjust_balance(op.fee_payer(), -fee_from_account); + db().adjust_balance(op.fee_payer(), -fee_from_account); - return result; - } + return result; + } }; } } diff --git a/libraries/chain/include/graphene/chain/fork_database.hpp b/libraries/chain/include/graphene/chain/fork_database.hpp index 4567b56e..31cd89f0 100644 --- a/libraries/chain/include/graphene/chain/fork_database.hpp +++ b/libraries/chain/include/graphene/chain/fork_database.hpp @@ -58,7 +58,7 @@ namespace graphene { namespace chain { class fork_database { public: - typedef vector branch_type; + typedef vector branch_type; fork_database(); void reset(); @@ -81,8 +81,8 @@ namespace graphene { namespace chain { pair< branch_type, branch_type > fetch_branch_from( block_id_type first, block_id_type second )const; - struct block_id{}; - struct block_num{}; + struct block_id; + struct block_num; typedef multi_index_container< item_ptr, indexed_by< diff --git a/libraries/chain/include/graphene/chain/operations.hpp b/libraries/chain/include/graphene/chain/operations.hpp index 6d910037..4f94757d 100644 --- a/libraries/chain/include/graphene/chain/operations.hpp +++ b/libraries/chain/include/graphene/chain/operations.hpp @@ -130,14 +130,19 @@ namespace graphene { namespace chain { }; /** - * This operation will claim all initial balance objects owned by any of the addresses and - * deposit them into the deposit_to_account. + * @brief Claim a balance in a @ref balanc_object + * + * This operation is used to claim the balance in a given @ref balance_object. If the balance object contains a + * vesting balance, @ref total_claimed must be set to zero, and all available vested funds will be claimed. If the + * object contains a non-vesting balance, @ref total_claimed must be the full balance of the object. + * + * This operation returns the total amount claimed. */ struct balance_claim_operation { asset fee; account_id_type deposit_to_account; - flat_set
owners; + balance_id_type balance_to_claim; asset total_claimed; account_id_type fee_payer()const { return deposit_to_account; } @@ -145,8 +150,8 @@ namespace graphene { namespace chain { share_type calculate_fee(const fee_schedule_type& k)const { return 0; } void validate()const; - void get_balance_delta(balance_accumulator& acc, const operation_result& result = asset())const { - acc.adjust(fee_payer(), total_claimed); + void get_balance_delta(balance_accumulator& acc, const operation_result& result)const { + acc.adjust(deposit_to_account, result.get()); acc.adjust(fee_payer(), -fee); } }; @@ -1646,7 +1651,7 @@ FC_REFLECT( graphene::chain::custom_operation, (fee)(payer)(required_auths)(id)( FC_REFLECT( graphene::chain::assert_operation, (fee)(fee_paying_account)(predicates)(required_auths) ) FC_REFLECT( graphene::chain::void_result, ) -FC_REFLECT( graphene::chain::balance_claim_operation, (fee)(deposit_to_account)(owners)(total_claimed) ) +FC_REFLECT( graphene::chain::balance_claim_operation, (fee)(deposit_to_account)(balance_to_claim)(total_claimed) ) FC_REFLECT_TYPENAME( graphene::chain::operation ) FC_REFLECT_TYPENAME( graphene::chain::operation_result ) diff --git a/libraries/chain/include/graphene/chain/transaction.hpp b/libraries/chain/include/graphene/chain/transaction.hpp index 5bc96a66..93c106e6 100644 --- a/libraries/chain/include/graphene/chain/transaction.hpp +++ b/libraries/chain/include/graphene/chain/transaction.hpp @@ -153,16 +153,8 @@ namespace graphene { namespace chain { vector signatures; -// flat_map signatures; - - /** some operations may depend only upon a signature and not - * require account approval. This allows those extra signatures - * to be added to the transaction. - */ -// flat_map extra_signatures; - /// Removes all operations and signatures - void clear() { operations.clear(); signatures.clear(); /*extra_signatures.clear();*/ } + void clear() { operations.clear(); signatures.clear(); } }; /** diff --git a/libraries/chain/include/graphene/chain/transaction_object.hpp b/libraries/chain/include/graphene/chain/transaction_object.hpp index 8cec3797..06ed0e74 100644 --- a/libraries/chain/include/graphene/chain/transaction_object.hpp +++ b/libraries/chain/include/graphene/chain/transaction_object.hpp @@ -34,12 +34,9 @@ namespace graphene { namespace chain { using boost::multi_index_container; using namespace boost::multi_index; /** - * The purpose of this object is to enable the detection - * of duplicate transactions. When a transaction is - * included in a block a transaction_object is - * added. At the end of block processing all - * transaction_objects that have expired can - * be removed from the index. + * The purpose of this object is to enable the detection of duplicate transactions. When a transaction is included + * in a block a transaction_object is added. At the end of block processing all transaction_objects that have + * expired can be removed from the index. */ class transaction_object : public abstract_object { @@ -52,7 +49,6 @@ namespace graphene { namespace chain { transaction_id_type trx_id; }; - struct by_expiration; struct by_id; struct by_trx_id; @@ -66,7 +62,6 @@ namespace graphene { namespace chain { > transaction_multi_index_type; typedef generic_index transaction_index; - } } FC_REFLECT_DERIVED( graphene::chain::transaction_object, (graphene::db::object), (trx)(expiration) ) diff --git a/libraries/chain/include/graphene/chain/types.hpp b/libraries/chain/include/graphene/chain/types.hpp index 71b36ed6..b9277c72 100644 --- a/libraries/chain/include/graphene/chain/types.hpp +++ b/libraries/chain/include/graphene/chain/types.hpp @@ -177,7 +177,7 @@ namespace graphene { namespace chain { typedef object_id< protocol_ids, withdraw_permission_object_type,withdraw_permission_object> withdraw_permission_id_type; typedef object_id< protocol_ids, vesting_balance_object_type, vesting_balance_object> vesting_balance_id_type; typedef object_id< protocol_ids, worker_object_type, worker_object> worker_id_type; - typedef object_id< protocol_ids, balance_object_type, balance_object> balance_id_type; + typedef object_id< protocol_ids, balance_object_type, balance_object> balance_id_type; typedef object_id< relative_protocol_ids, key_object_type, key_object> relative_key_id_type; typedef object_id< relative_protocol_ids, account_object_type, account_object> relative_account_id_type; diff --git a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp index d8019d7d..db20091f 100644 --- a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp +++ b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp @@ -44,24 +44,26 @@ namespace graphene { namespace chain { }; /** - * @brief Linear vesting balance with cliff + * @brief Linear vesting balance with cliff * * This vesting balance type is used to mimic traditional stock vesting contracts where - * each day a certain amount vests until it is fully matured. + * each day a certain amount vests until it is fully matured. * * @note New funds may not be added to a linear vesting balance. */ struct linear_vesting_policy { - uint32_t vesting_seconds = 0; ///< must be greater than zero - /** while coindays may accrue over time, none may be claimed before first_claim date */ - fc::time_point_sec start_claim; - /** linear vesting may begin prior to allowing the user to actually claim the funds, this - * can be used to create a cliff. - */ - fc::time_point_sec begin_date; - share_type begin_balance; ///< same asset as balance - share_type total_withdrawn; ///< same asset as balance + /// No amount may be withdrawn before this time, regardless of how much has vested. + fc::time_point_sec earliest_withdraw_time; + /// This is the time at which funds begin vesting. + /// Note that withdrawals are still not available until @ref earliest_withdraw_time + fc::time_point_sec begin_date; + /// Duration of vesting period, in seconds. Must be greater than zero. + uint32_t vesting_seconds = 0; + /// The total amount of asset to vest + share_type begin_balance; + /// The total amount of asset which has already been withdrawn + share_type total_withdrawn; asset get_allowed_withdraw(const vesting_policy_context& ctx)const; bool is_deposit_allowed(const vesting_policy_context& ctx)const; @@ -73,20 +75,19 @@ namespace graphene { namespace chain { void on_withdraw(const vesting_policy_context& ctx); }; - /** * @brief defines vesting in terms of coin-days accrued which allows for dynamic deposit/withdraw * * The economic effect of this vesting policy is to require a certain amount of "interest" to accrue * before the full balance may be withdrawn. Interest accrues as coindays (balance * length held). If - * some of the balance is withdrawn, the remaining balance must be held longer. + * some of the balance is withdrawn, the remaining balance must be held longer. */ struct cdd_vesting_policy { uint32_t vesting_seconds = 0; fc::uint128_t coin_seconds_earned; /** while coindays may accrue over time, none may be claimed before first_claim date */ - fc::time_point_sec start_claim; + fc::time_point_sec start_claim; fc::time_point_sec coin_seconds_earned_last_update; /** @@ -117,7 +118,6 @@ namespace graphene { namespace chain { cdd_vesting_policy > vesting_policy; - /** * Vesting balance object is a balance that is locked by the blockchain for a period of time. */ @@ -127,15 +127,17 @@ namespace graphene { namespace chain { static const uint8_t space_id = protocol_ids; static const uint8_t type_id = vesting_balance_object_type; - account_id_type owner; - asset balance; - vesting_policy policy; + /// Account which owns and may withdraw from this vesting balance + account_id_type owner; + /// Total amount remaining in this vesting balance + /// Includes the unvested funds, and the vested funds which have not yet been withdrawn + asset balance; + /// The vesting policy stores details on when funds vest, and controls when they may be withdrawn + vesting_policy policy; vesting_balance_object() {} - /** - * Used to increase existing vesting balances. - */ + ///@brief Deposit amount into vesting balance, requiring it to vest before withdrawal void deposit(const fc::time_point_sec& now, const asset& amount); bool is_deposit_allowed(const fc::time_point_sec& now, const asset& amount)const; @@ -144,12 +146,12 @@ namespace graphene { namespace chain { bool is_deposit_vested_allowed(const fc::time_point_sec& now, const asset& amount)const; /** - * Used to remove a vesting balance from the VBO. As well - * as the balance field, coin_seconds_earned and + * Used to remove a vesting balance from the VBO. As well as the + * balance field, coin_seconds_earned and * coin_seconds_earned_last_update fields are updated. * - * The money doesn't "go" anywhere; the caller is responsible - * for crediting it to the proper account. + * The money doesn't "go" anywhere; the caller is responsible for + * crediting it to the proper account. */ void withdraw(const fc::time_point_sec& now, const asset& amount); bool is_withdraw_allowed(const fc::time_point_sec& now, const asset& amount)const; @@ -158,9 +160,9 @@ namespace graphene { namespace chain { } } // graphene::chain FC_REFLECT(graphene::chain::linear_vesting_policy, - (vesting_seconds) - (start_claim) + (earliest_withdraw_time) (begin_date) + (vesting_seconds) (begin_balance) (total_withdrawn) ) diff --git a/libraries/chain/operations.cpp b/libraries/chain/operations.cpp index 983b6174..a3b50c08 100644 --- a/libraries/chain/operations.cpp +++ b/libraries/chain/operations.cpp @@ -939,8 +939,6 @@ void balance_claim_operation::get_required_auth(flat_set& acti void balance_claim_operation::validate()const { - FC_ASSERT( owners.size() > 0 ); - FC_ASSERT( total_claimed.amount > 0 ); FC_ASSERT( fee == asset() ); } diff --git a/libraries/chain/vesting_balance_evaluator.cpp b/libraries/chain/vesting_balance_evaluator.cpp index fc1d2a95..e43a535f 100644 --- a/libraries/chain/vesting_balance_evaluator.cpp +++ b/libraries/chain/vesting_balance_evaluator.cpp @@ -56,7 +56,7 @@ struct init_policy_visitor linear_vesting_policy policy; policy.vesting_seconds = i.vesting_seconds; policy.begin_date = i.begin_date; - policy.start_claim = i.start_claim; + policy.earliest_withdraw_time = i.start_claim; policy.begin_balance = init_balance; p = policy; } diff --git a/libraries/chain/vesting_balance_object.cpp b/libraries/chain/vesting_balance_object.cpp index f57920c2..a7242947 100644 --- a/libraries/chain/vesting_balance_object.cpp +++ b/libraries/chain/vesting_balance_object.cpp @@ -25,13 +25,12 @@ inline bool sum_below_max_shares(const asset& a, const asset& b) assert(GRAPHENE_MAX_SHARE_SUPPLY + GRAPHENE_MAX_SHARE_SUPPLY > GRAPHENE_MAX_SHARE_SUPPLY); return (a.amount <= GRAPHENE_MAX_SHARE_SUPPLY) && ( b.amount <= GRAPHENE_MAX_SHARE_SUPPLY) - && ((a.amount + b.amount) <= GRAPHENE_MAX_SHARE_SUPPLY) - ; + && ((a.amount + b.amount) <= GRAPHENE_MAX_SHARE_SUPPLY); } asset linear_vesting_policy::get_allowed_withdraw(const vesting_policy_context& ctx) const { - if(ctx.now <= start_claim) + if(ctx.now <= earliest_withdraw_time) return asset(0, ctx.balance.asset_id); if(ctx.now <= begin_date) return asset(0, ctx.balance.asset_id); diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index b558b7b1..ac63eb4b 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -277,7 +277,7 @@ public: { // Right now the wallet_api has no way of knowing if the connection to the // witness has already disconnected (via the witness node exiting first). - // If it has exited, cancel_all_subscriptsions() will throw and there's + // If it has exited, cancel_all_subscriptsions() will throw and there's // nothing we can do about it. // dlog("Caught exception ${e} while canceling database subscriptions", ("e", e)); } @@ -892,7 +892,7 @@ public: return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (symbol)(new_issuer)(new_options)(broadcast) ) } - + signed_transaction update_bitasset(string symbol, asset_object::bitasset_options new_options, bool broadcast /* = false */) @@ -913,7 +913,7 @@ public: return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (symbol)(new_options)(broadcast) ) } - + signed_transaction update_asset_feed_producers(string symbol, flat_set new_feed_producers, bool broadcast /* = false */) @@ -959,7 +959,7 @@ public: return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (publishing_account)(symbol)(feed)(broadcast) ) } - + signed_transaction fund_asset_fee_pool(string from, string symbol, string amount, @@ -983,7 +983,7 @@ public: return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (from)(symbol)(amount)(broadcast) ) } - + signed_transaction burn_asset(string from, string amount, string symbol, @@ -1005,7 +1005,7 @@ public: return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (from)(amount)(symbol)(broadcast) ) } - + signed_transaction global_settle_asset(string symbol, price settle_price, bool broadcast /* = false */) @@ -1065,7 +1065,7 @@ public: return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (authorizing_account)(account_to_list)(new_listing_status)(broadcast) ) } - + signed_transaction create_delegate(string owner_account, bool broadcast /* = false */) { try { @@ -1133,7 +1133,7 @@ public: return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (voting_account)(delegate)(approve)(broadcast) ) } - + signed_transaction vote_for_witness(string voting_account, string witness, bool approve, @@ -1167,7 +1167,7 @@ public: return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (voting_account)(witness)(approve)(broadcast) ) } - + signed_transaction set_voting_proxy(string account_to_modify, optional voting_account, bool broadcast /* = false */) @@ -1186,7 +1186,7 @@ public: FC_THROW("Account ${account} is already voting for itself", ("account", account_to_modify)); account_object_to_modify.options.voting_account = account_id_type(); } - + account_update_operation account_update_op; account_update_op.account = account_object_to_modify.id; account_update_op.new_options = account_object_to_modify.options; @@ -1849,7 +1849,7 @@ signed_transaction wallet_api::update_asset(string symbol, { return my->update_asset(symbol, new_issuer, new_options, broadcast); } - + signed_transaction wallet_api::update_bitasset(string symbol, asset_object::bitasset_options new_options, bool broadcast /* = false */) @@ -1937,7 +1937,7 @@ signed_transaction wallet_api::vote_for_witness(string voting_account, bool broadcast /* = false */) { return my->vote_for_witness(voting_account, witness, approve, broadcast); -} +} signed_transaction wallet_api::set_voting_proxy(string account_to_modify, optional voting_account, @@ -2148,18 +2148,17 @@ signed_transaction wallet_api::import_balance( string name_or_id, const vector_remote_db->get_global_properties().parameters.current_fees ) ); @@ -2170,7 +2169,7 @@ signed_transaction wallet_api::import_balance( string name_or_id, const vector_remote_net->broadcast_transaction(tx); return tx; diff --git a/programs/js_operation_serializer/main.cpp b/programs/js_operation_serializer/main.cpp index eab7181c..32fdbbb8 100644 --- a/programs/js_operation_serializer/main.cpp +++ b/programs/js_operation_serializer/main.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index d6d0e78d..0be99d5f 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -66,7 +66,10 @@ database_fixture::database_fixture() for( int i = 0; i < 10; ++i ) { auto name = "init"+fc::to_string(i); - genesis_state.initial_accounts.emplace_back(name, delegate_priv_key.get_public_key(), true); + genesis_state.initial_accounts.emplace_back(name, + delegate_priv_key.get_public_key(), + delegate_priv_key.get_public_key(), + true); genesis_state.initial_committee.push_back({name}); genesis_state.initial_witnesses.push_back({name, delegate_priv_key.get_public_key(), secret}); } diff --git a/tests/tests/block_tests.cpp b/tests/tests/block_tests.cpp index a9dbf581..59f31146 100644 --- a/tests/tests/block_tests.cpp +++ b/tests/tests/block_tests.cpp @@ -45,7 +45,10 @@ genesis_state_type make_genesis() { for( int i = 0; i < 10; ++i ) { auto name = "init"+fc::to_string(i); - genesis_state.initial_accounts.emplace_back(name, delegate_priv_key.get_public_key(), true); + genesis_state.initial_accounts.emplace_back(name, + delegate_priv_key.get_public_key(), + delegate_priv_key.get_public_key(), + true); genesis_state.initial_committee.push_back({name}); genesis_state.initial_witnesses.push_back({name, delegate_priv_key.get_public_key(), secret}); } diff --git a/tests/tests/operation_tests2.cpp b/tests/tests/operation_tests2.cpp index d0699737..86e660aa 100644 --- a/tests/tests/operation_tests2.cpp +++ b/tests/tests/operation_tests2.cpp @@ -981,31 +981,32 @@ BOOST_AUTO_TEST_CASE( balance_object_test ) fc::temp_directory td; genesis_state.initial_balances.push_back({generate_private_key("n").get_public_key(), GRAPHENE_SYMBOL, 1}); genesis_state.initial_balances.push_back({generate_private_key("x").get_public_key(), GRAPHENE_SYMBOL, 1}); - genesis_state.initial_accounts.emplace_back("n", generate_private_key("n").get_public_key(), false); + // TODO: vesting genesis balances + genesis_state.initial_accounts.emplace_back("n", generate_private_key("n").get_public_key()); db.open(td.path(), genesis_state); - const balance_object& balance = *db.get_index_type().indices().find(balance_id_type()); + const balance_object& balance = balance_id_type()(db); BOOST_CHECK_EQUAL(balance.balance.amount.value, 1); - BOOST_CHECK_EQUAL(db.get_index_type().indices().find(balance_id_type(1))->balance.amount.value, 1); + BOOST_CHECK_EQUAL(balance_id_type(1)(db).balance.amount.value, 1); balance_claim_operation op; op.deposit_to_account = db.get_index_type().indices().get().find("n")->get_id(); op.total_claimed = asset(1); - op.owners.insert(genesis_state.initial_balances.back().owner); + op.balance_to_claim = balance_id_type(1); trx.operations = {op}; trx.sign(generate_private_key("n")); - //trx.sign(generate_private_key("n")); - // Fail because I'm claiming the wrong address + // Fail because I'm claiming from an address which hasn't signed BOOST_CHECK_THROW(db.push_transaction(trx), fc::exception); trx.clear(); - op.owners = {genesis_state.initial_balances.front().owner}; + op.balance_to_claim = balance_id_type(); trx.operations = {op}; trx.sign(generate_private_key("n")); - //trx.sign(generate_private_key("n")); db.push_transaction(trx); // Not using fixture's get_balance() here because it uses fixture's db, not my override BOOST_CHECK_EQUAL(db.get_balance(op.deposit_to_account, asset_id_type()).amount.value, 1); + BOOST_CHECK(db.find_object(balance_id_type()) == nullptr); + BOOST_CHECK(db.find_object(balance_id_type(1)) != nullptr); } FC_LOG_AND_RETHROW() } // TODO: Write linear VBO tests