diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index 674de46c43..4b49a56b4e 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -36,8 +36,57 @@ #include #include +#include + +namespace graphene { + namespace protocol { + void verify_cycled_authority( const account_id_type& id, + const std::function& get_active, + const std::function& get_owner, + uint32_t max_recursion_depth ); + } + namespace chain { + +namespace detail { + + template + void check_account_authorities(const account_id_type account_id, + const DB& db, + const optional& active, + const optional& owner) + { + const auto empty_auth = authority{}; + const auto no_account = account_id_type{}; -namespace graphene { namespace chain { + auto get_active = [&account_id, &db, &active, &owner, &empty_auth, &no_account]( account_id_type id ) + { + if ( (no_account == id) || (account_id == id) ) + { + if (active) + { + return &(*active); + } + return &empty_auth; + } + return &id(db).active; + }; + + auto get_owner = [&account_id, &db, &active, &owner, &empty_auth, &no_account]( account_id_type id ) + { + if ( (no_account == id) || (account_id == id) ) + { + if (owner) + { + return &(*owner); + } + return &empty_auth; + } + return &id(db).owner; + }; + + verify_cycled_authority(account_id, get_active, get_owner, db.get_global_properties().parameters.max_authority_depth); + }; +} //detail void verify_authority_accounts( const database& db, const authority& a ) { @@ -133,6 +182,11 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio { verify_authority_accounts( d, op.owner ); verify_authority_accounts( d, op.active ); + + if( d.head_block_time() >= HARDFORK_CYCLED_ACCOUNTS_TIME ) + { + detail::check_account_authorities({}, d, op.active, op.owner); + } } GRAPHENE_RECODE_EXC( internal_verify_auth_max_auth_exceeded, account_create_max_auth_exceeded ) GRAPHENE_RECODE_EXC( internal_verify_auth_account_not_found, account_create_auth_account_not_found ) @@ -276,8 +330,30 @@ void_result account_update_evaluator::do_evaluate( const account_update_operatio try { - if( o.owner ) verify_authority_accounts( d, *o.owner ); - if( o.active ) verify_authority_accounts( d, *o.active ); + if( o.owner ) + { + verify_authority_accounts( d, *o.owner ); + } + if( o.active ) + { + verify_authority_accounts( d, *o.active ); + } + + try + { + detail::check_account_authorities(o.account, d, o.active, o.owner); + } + catch (tx_missing_active_auth) + { + if( d.head_block_time() < HARDFORK_CYCLED_ACCOUNTS_TIME ) + { + cycle_detected = true; + } + else + { + throw; + } + } } GRAPHENE_RECODE_EXC( internal_verify_auth_max_auth_exceeded, account_update_max_auth_exceeded ) GRAPHENE_RECODE_EXC( internal_verify_auth_account_not_found, account_update_auth_account_not_found ) @@ -316,7 +392,12 @@ void_result account_update_evaluator::do_apply( const account_update_operation& } // update account object - d.modify( *acnt, [&o](account_object& a){ + d.modify( *acnt, [&o, this](account_object& a){ + if( cycle_detected && !a.stable_owner.valid()) + { + a.stable_owner = a.owner; + } + if( o.owner ) { a.owner = *o.owner; @@ -360,6 +441,44 @@ void_result account_update_evaluator::do_apply( const account_update_operation& return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } +void_result account_unlock_evaluator::do_evaluate( const account_unlock_operation& o ) +{ try { + database& d = db(); + FC_ASSERT(d.head_block_time() >= HARDFORK_CYCLED_ACCOUNTS_TIME, + "Unlocking account is available after HARDFORK_CYCLED_ACCOUNTS_TIME only!" + ); + + acnt = &o.account_to_unlock(d); + FC_ASSERT( acnt->stable_owner.valid(), "Account ${a} is not unlockable.", ("a", o.account_to_unlock) ); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } + +void_result account_unlock_evaluator::do_apply( const account_unlock_operation& o ) +{ try { + database& d = db(); + + // update account object + d.modify( *acnt, [&o, this](account_object& a){ + a.owner = *a.stable_owner; + a.stable_owner.reset(); + }); + + const auto& bal_idx = d.get_index_type< primary_index< account_balance_index > >().get_secondary_index< balances_by_account_index >(); + for( const auto& entry : bal_idx.get_account_balances( acnt->get_id() ) ) + { + const auto balance = entry.second->get_balance(); + const auto unlock_cost = balance.amount / 10; + const auto penalty = asset(unlock_cost, balance.asset_id); + + d.adjust_balance(acnt->get_id(), -penalty); + d.adjust_balance(GRAPHENE_COMMITTEE_ACCOUNT, penalty); + d.push_applied_operation(account_unlock_penalty_payment_operation( acnt->get_id(), penalty )); + } + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } + void_result account_whitelist_evaluator::do_evaluate(const account_whitelist_operation& o) { try { database& d = db(); diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 3d0aaf8c04..be68f2f66c 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -135,6 +135,7 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index d17bec6821..e849d70d29 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -61,6 +61,11 @@ struct get_impacted_account_visitor { _impacted.insert( op.fee_payer() ); // account_id } + void operator()(const account_unlock_penalty_payment_operation& op) + { + _impacted.insert( op.fee_payer() ); + _impacted.insert( GRAPHENE_COMMITTEE_ACCOUNT ); + } void operator()( const execute_bid_operation& op ) { _impacted.insert( op.fee_payer() ); // bidder @@ -80,6 +85,10 @@ struct get_impacted_account_visitor if( op.active ) add_authority_accounts( _impacted, *(op.active) ); } + void operator()( const account_unlock_operation& op ) + { + _impacted.insert( op.fee_payer() ); // account_to_unlock + } void operator()( const account_whitelist_operation& op ) { _impacted.insert( op.fee_payer() ); // authorizing_account diff --git a/libraries/chain/hardfork.d/CYCLED_ACCOUNTS_HF.hf b/libraries/chain/hardfork.d/CYCLED_ACCOUNTS_HF.hf new file mode 100644 index 0000000000..82baae2d69 --- /dev/null +++ b/libraries/chain/hardfork.d/CYCLED_ACCOUNTS_HF.hf @@ -0,0 +1,4 @@ +// #HARDFORK_CYCLED_ACCOUNTS_TIME Prevent to create/update account authorities that locks account. +#ifndef HARDFORK_CYCLED_ACCOUNTS_TIME +#define HARDFORK_CYCLED_ACCOUNTS_TIME (fc::time_point_sec( 1600000000 )) // Sunday, September 13, 2020 12:26:40 PM +#endif diff --git a/libraries/chain/include/graphene/chain/account_evaluator.hpp b/libraries/chain/include/graphene/chain/account_evaluator.hpp index e543c90dab..c69f30a999 100644 --- a/libraries/chain/include/graphene/chain/account_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/account_evaluator.hpp @@ -45,6 +45,7 @@ class account_update_evaluator : public evaluator void_result do_apply( const account_update_operation& o ); const account_object* acnt; + bool cycle_detected = false; }; class account_upgrade_evaluator : public evaluator @@ -58,6 +59,17 @@ class account_upgrade_evaluator : public evaluator const account_object* account; }; +class account_unlock_evaluator : public evaluator +{ +public: + typedef account_unlock_operation operation_type; + + void_result do_evaluate( const account_unlock_operation& o ); + void_result do_apply( const account_unlock_operation& o ); + + const account_object* acnt; +}; + class account_whitelist_evaluator : public evaluator { public: diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index 70aa0f78e9..5cde51e3b0 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -190,6 +190,7 @@ namespace graphene { namespace chain { * update the active authority. */ authority owner; + optional stable_owner; /// The owner authority contains the hot keys of the account. This authority has control over nearly all /// operations the account may perform. authority active; diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 46a8feb54e..3e1e39dd8c 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -30,8 +30,7 @@ #define GRAPHENE_MAX_NESTED_OBJECTS (200) -#define GRAPHENE_CURRENT_DB_VERSION "20190503" +#define GRAPHENE_CURRENT_DB_VERSION "20191231" #define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT 4 #define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT 3 - diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 4e9a3ea656..4104048b95 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -110,6 +110,10 @@ struct proposal_operation_hardfork_visitor void operator()(const graphene::chain::htlc_extend_operation &op) const { FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); } + // hf_cycled_accounts + void operator()(const graphene::chain::account_unlock_operation &op) const { + FC_ASSERT( block_time >= HARDFORK_CYCLED_ACCOUNTS_TIME, "Not allowed until hardfork CYCLED_ACCOUNTS" ); + } // loop and self visit in proposals void operator()(const graphene::chain::proposal_create_operation &v) const { bool already_contains_proposal_update = false; diff --git a/libraries/protocol/account.cpp b/libraries/protocol/account.cpp index f51271405b..77b7b6e7c0 100644 --- a/libraries/protocol/account.cpp +++ b/libraries/protocol/account.cpp @@ -258,6 +258,17 @@ void account_update_operation::validate()const validate_special_authority( *extensions.value.active_special_authority ); } +share_type account_unlock_operation::calculate_fee( const fee_parameters_type& k ) const +{ + auto core_fee_required = k.fee; + return core_fee_required; +} + +void account_unlock_operation::validate() const +{ + FC_ASSERT( fee.amount >= 0 ); +} + share_type account_upgrade_operation::calculate_fee(const fee_parameters_type& k) const { if( upgrade_to_lifetime_member ) @@ -284,9 +295,12 @@ GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_create_op GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_whitelist_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_update_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_upgrade_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_unlock_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_transfer_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_create_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_whitelist_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_update_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_upgrade_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_unlock_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_transfer_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_unlock_penalty_payment_operation ) diff --git a/libraries/protocol/include/graphene/protocol/account.hpp b/libraries/protocol/include/graphene/protocol/account.hpp index 02aba3a3f7..adb9ccfd7c 100644 --- a/libraries/protocol/include/graphene/protocol/account.hpp +++ b/libraries/protocol/include/graphene/protocol/account.hpp @@ -166,6 +166,63 @@ namespace graphene { namespace protocol { { if( !is_owner_update() ) a.insert( account ); } }; + /** + * @ingroup operations + * @brief Fix locked account and restore access + * + * This operation is used to unlock account being blocked. + */ + struct account_unlock_operation : public base_operation + { + struct fee_parameters_type + { + share_type fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; + account_id_type account_to_unlock; + authority previous_authority; + extensions_type extensions; + + account_id_type fee_payer() const { return account_to_unlock; } + void validate() const; + share_type calculate_fee( const fee_parameters_type& k ) const; + + void get_required_active_authorities( flat_set& active) const + { + // a little bit tricky, see operations.cpp : operation_get_required_auth + // active.insert( v.fee_payer() ); - fee_payer added explicitly as active authority + // but we needn't it to authorize because it is locked + active.erase( account_to_unlock ); + } + + void get_required_authorities( vector& a ) const + { + a.push_back( previous_authority ); + } + }; + + struct account_unlock_penalty_payment_operation : public base_operation + { + struct fee_parameters_type {}; + + account_unlock_penalty_payment_operation(){} + account_unlock_penalty_payment_operation( + account_id_type a, asset p + ) + :account_id(a), penalty(p), fee() {} + + account_id_type account_id; + asset penalty; + asset fee; + + account_id_type fee_payer()const { return account_id; } + void validate()const { FC_ASSERT( !"virtual operation" ); } + + /// This is a virtual operation; there is no fee + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + /** * @brief This operation is used to whitelist and blacklist accounts, primarily for transacting in whitelisted assets * @ingroup operations @@ -288,27 +345,35 @@ FC_REFLECT( graphene::protocol::account_update_operation, (fee)(account)(owner)(active)(new_options)(extensions) ) +FC_REFLECT( graphene::protocol::account_unlock_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::account_unlock_operation, (fee)(account_to_unlock)(previous_authority)(extensions) ) + + FC_REFLECT( graphene::protocol::account_upgrade_operation, (fee)(account_to_upgrade)(upgrade_to_lifetime_member)(extensions) ) FC_REFLECT( graphene::protocol::account_whitelist_operation, (fee)(authorizing_account)(account_to_list)(new_listing)(extensions)) - FC_REFLECT( graphene::protocol::account_create_operation::fee_parameters_type, (basic_fee)(premium_fee)(price_per_kbyte) ) FC_REFLECT( graphene::protocol::account_whitelist_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::protocol::account_update_operation::fee_parameters_type, (fee)(price_per_kbyte) ) FC_REFLECT( graphene::protocol::account_upgrade_operation::fee_parameters_type, (membership_annual_fee)(membership_lifetime_fee) ) FC_REFLECT( graphene::protocol::account_transfer_operation::fee_parameters_type, (fee) ) - FC_REFLECT( graphene::protocol::account_transfer_operation, (fee)(account_id)(new_owner)(extensions) ) +FC_REFLECT( graphene::protocol::account_unlock_penalty_payment_operation, (fee)(account_id)(penalty) ) +FC_REFLECT( graphene::protocol::account_unlock_penalty_payment_operation::fee_parameters_type, ) // VIRTUAL GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_options ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_create_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_whitelist_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_update_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_upgrade_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_unlock_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_transfer_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_create_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_whitelist_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_update_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_upgrade_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_unlock_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_transfer_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_unlock_penalty_payment_operation ) + diff --git a/libraries/protocol/include/graphene/protocol/operations.hpp b/libraries/protocol/include/graphene/protocol/operations.hpp index 3a603ebf64..28934df0d3 100644 --- a/libraries/protocol/include/graphene/protocol/operations.hpp +++ b/libraries/protocol/include/graphene/protocol/operations.hpp @@ -101,7 +101,9 @@ namespace graphene { namespace protocol { htlc_redeem_operation, htlc_redeemed_operation, // VIRTUAL htlc_extend_operation, - htlc_refund_operation // VIRTUAL + htlc_refund_operation, // VIRTUAL + account_unlock_operation, + account_unlock_penalty_payment_operation //VIRTUAL > operation; /// @} // operations group diff --git a/libraries/protocol/transaction.cpp b/libraries/protocol/transaction.cpp index 42b1d8ee0f..4ab92931bb 100644 --- a/libraries/protocol/transaction.cpp +++ b/libraries/protocol/transaction.cpp @@ -122,9 +122,11 @@ struct sign_state auto itr = provided_signatures.find(k); if( itr == provided_signatures.end() ) { - auto pk = available_keys.find(k); - if( pk != available_keys.end() ) + if( is_key_available(k) ) + { return provided_signatures[k] = true; + } + return false; } return itr->second = true; @@ -156,10 +158,13 @@ struct sign_state if( itr == provided_address_sigs->end() ) { auto aitr = available_address_sigs->find(a); - if( aitr != available_address_sigs->end() ) { - auto pk = available_keys.find(aitr->second); - if( pk != available_keys.end() ) + if( aitr != available_address_sigs->end() ) + { + if( is_key_available(aitr->second) ) + { return provided_signatures[aitr->second] = true; + } + return false; } } @@ -172,6 +177,17 @@ struct sign_state return check_authority( get_active(id) ) || ( allow_non_immediate_owner && check_authority( get_owner(id) ) ); } + bool is_key_available(const public_key_type &key) + { + if (verify_keyset) + { + return true; + } + + auto pk = available_keys.find(key); + return (pk != available_keys.end()); + } + /** * Checks to see if we have signatures of the active authorites of * the accounts specified in authority or the keys specified. @@ -240,12 +256,14 @@ struct sign_state const std::function& owner, bool allow_owner, uint32_t max_recursion_depth = GRAPHENE_MAX_SIG_CHECK_DEPTH, - const flat_set& keys = empty_keyset ) + const flat_set& keys = empty_keyset, + const bool verify = false ) : get_active(active), get_owner(owner), allow_non_immediate_owner(allow_owner), max_recursion(max_recursion_depth), - available_keys(keys) + available_keys(keys), + verify_keyset(verify) { for( const auto& key : sigs ) provided_signatures[ key ] = false; @@ -258,12 +276,27 @@ struct sign_state const bool allow_non_immediate_owner; const uint32_t max_recursion; const flat_set& available_keys; + const bool verify_keyset; flat_map provided_signatures; flat_set approved_by; }; +void verify_cycled_authority( const account_id_type& id, + const std::function& get_active, + const std::function& get_owner, + uint32_t max_recursion_depth ) +{ try { + sign_state s( empty_keyset, get_active, get_owner, true, max_recursion_depth, empty_keyset, true ); + + GRAPHENE_ASSERT( s.check_authority(id), + tx_missing_active_auth, + "Missing Authority ${id}", + ("id",id)("auth",*get_active(id))("owner",*get_owner(id)) + ); +} FC_CAPTURE_AND_RETHROW( (id)(max_recursion_depth) ) } + void verify_authority( const vector& ops, const flat_set& sigs, const std::function& get_active, const std::function& get_owner, diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 1fc432ce1c..a19c20296a 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -818,6 +818,16 @@ class wallet_api */ signed_transaction upgrade_account(string name, bool broadcast); + /** + * Fix locked account. + * This restores access to locked account. + * + * @param name the name or id of the account to unlock + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction unlocking the account + */ + signed_transaction fix_locked_account(string name, bool broadcast = false); + /** Creates a new account and registers it on the blockchain. * * @todo why no referrer_percent here? @@ -1846,6 +1856,7 @@ FC_API( graphene::wallet::wallet_api, (derive_owner_keys_from_brain_key) (register_account) (upgrade_account) + (fix_locked_account) (create_account_with_brain_key) (sell_asset) (borrow_asset) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 2f964bb811..19053b995c 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -1184,6 +1184,23 @@ class wallet_api_impl return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (name) ) } + signed_transaction fix_locked_account(string name, bool broadcast) + { try { + FC_ASSERT( !self.is_locked() ); + account_object account_obj = get_account(name); + FC_ASSERT( account_obj.stable_owner, "Account is not unlockable" ); + + signed_transaction tx; + account_unlock_operation op; + op.account_to_unlock = account_obj.get_id(); + op.previous_authority = *account_obj.stable_owner; + tx.operations = {op}; + set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees() ); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (name) ) } + // This function generates derived keys starting with index 0 and keeps incrementing // the index until it finds a key that isn't registered in the block chain. To be // safer, it continues checking for a few more keys to make sure there wasn't a short gap @@ -4434,6 +4451,11 @@ signed_transaction wallet_api::upgrade_account( string name, bool broadcast ) return my->upgrade_account(name,broadcast); } +signed_transaction wallet_api::fix_locked_account(string name, bool broadcast ) +{ + return my->fix_locked_account(name, broadcast); +} + signed_transaction wallet_api::sell_asset(string seller_account, string amount_to_sell, string symbol_to_sell, diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index ce5255f4e8..9e6a3673b1 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -962,6 +962,928 @@ BOOST_AUTO_TEST_CASE( update_account ) } } +BOOST_AUTO_TEST_CASE( update_account_lead_to_cycled_authority_before_HARDFORK_CYCLED_ACCOUNTS_TIME_test ) +{ + /* + see https://github.com/bitshares/bsips/pull/149#discussion_r270407694 + Precondition: + There are three accounts Alice, Bob and Charlie + Steps: + 1. Alice sets Bob to her active and owner authority + 2. Charlie sets Alice to his active and owner authority + 3. Alice sets Charlie to her active and owner authority + 4. Bob sets Charlie to his active and owner authority + Expected: + Before HARDFORK_CYCLED_ACCOUNTS_TIME it should work + */ + try { + ACTORS( (bob) (alice) (charlie) ); + + auto update_auth = [&](const account_update_operation& op){ + trx.operations.clear(); + trx.operations.push_back(op); + PUSH_TX( db, trx, ~0 ); + }; + + { + account_update_operation op; + op.account = alice_id; + op.active = authority(1, bob_id, 1); + op.owner = op.active; + update_auth(op); + } + + { + account_update_operation op; + op.account = charlie_id; + op.active = authority(1, alice_id, 1); + op.owner = op.active; + update_auth(op); + } + + { + account_update_operation op; + op.account = alice_id; + op.active = authority(1, charlie_id, 1); + op.owner = op.active; + update_auth(op); + } + + { + account_update_operation op; + op.account = bob_id; + op.active = authority(1, charlie_id, 1); + op.owner = op.active; + update_auth(op); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( get_restore_point_of_cycled_account_test ) +{ + /* + see https://github.com/bitshares/bsips/pull/149#discussion_r270407694 + Precondition: + There are three accounts Alice, Bob and Charlie + Steps: + 1. Alice sets Bob to her active and owner authority + 2. Charlie sets Alice to his active and owner authority + 3. Alice sets Charlie to her active and owner authority + 4. Bob sets Charlie to his active and owner authority + Expected: + Restore point created during account_update_operation evaluation + */ + try { + ACTORS( (bob) (alice) (charlie) ); + + auto update_auth = [&](const account_update_operation& op){ + trx.operations.clear(); + trx.operations.push_back(op); + PUSH_TX( db, trx, ~0 ); + }; + + { + account_update_operation op; + op.account = alice_id; + op.active = authority(1, bob_id, 1); + op.owner = op.active; + update_auth(op); + } + + { + account_update_operation op; + op.account = charlie_id; + op.active = authority(1, alice_id, 1); + op.owner = op.active; + update_auth(op); + } + + { + account_update_operation op; + op.account = alice_id; + op.active = authority(1, charlie_id, 1); + op.owner = op.active; + update_auth(op); + } + + const auto alice_stable_owner = alice_id(db).stable_owner; + BOOST_REQUIRE(alice_stable_owner.valid()); + const auto alice_expected = authority(1, bob_id, 1); + BOOST_CHECK(*alice_stable_owner == alice_expected); + + { + account_update_operation op; + op.account = bob_id; + op.active = authority(1, charlie_id, 1); + op.owner = op.active; + update_auth(op); + } + + const auto bob_stable_owner = bob_id(db).stable_owner; + BOOST_REQUIRE(bob_stable_owner.valid()); + const auto bob_expected = authority(123, bob_public_key, 123); + BOOST_CHECK(*bob_stable_owner == bob_expected); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( get_restore_point_if_exceed_depth_limit_test ) +{ + try { + // Delegation chain with exceeded authority depth limit + // Steps: + // 1. bob->charlie + // 2. alice->bob + // Expected: + // db saves restore point for alice account (alice_public_key) + + ACTORS( (alice) (bob) (charlie) ); + + db.modify( db.get_global_properties(), [&]( global_property_object& gpo ) { + gpo.parameters.max_authority_depth = 1; + }); + + auto update_auth = [&](const account_update_operation& op){ + trx.operations.clear(); + trx.operations.push_back(op); + PUSH_TX( db, trx, ~0 ); + }; + + { + account_update_operation op; + op.account = bob_id; + op.active = authority(1, charlie_id, 1); + op.owner = op.active; + update_auth(op); + } + + { + account_update_operation op; + op.account = alice_id; + op.active = authority(1, bob_id, 1); + op.owner = op.active; + update_auth(op); + } + + const auto alice_stable_owner = alice_id(db).stable_owner; + BOOST_REQUIRE(alice_stable_owner.valid()); + const auto expected = authority(123, alice_public_key, 123); + BOOST_CHECK(*alice_stable_owner == expected); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( restore_point_not_created_if_account_not_locked_test ) +{ + try { + // Delegation chain + // Steps: + // 1. bob->charlie + // 2. alice->bob + // Expected: + // no restore point for alice account saved + + ACTORS( (alice) (bob) (charlie) ); + + auto update_auth = [&](const account_update_operation& op){ + trx.operations.clear(); + trx.operations.push_back(op); + PUSH_TX( db, trx, ~0 ); + }; + + { + account_update_operation op; + op.account = bob_id; + op.active = authority(1, charlie_id, 1); + op.owner = op.active; + update_auth(op); + } + + { + account_update_operation op; + op.account = alice_id; + op.active = authority(1, bob_id, 1); + op.owner = op.active; + update_auth(op); + } + + BOOST_REQUIRE(!alice_id(db).stable_owner.valid()); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( make_recursive_authority_test ) +{ + try { + ACTORS( (bob) (alice) ); + + // Alice sets Bob to her active and owner authority + { + account_update_operation op; + op.account = alice_id; + op.active = authority(1, bob_id, 1); + op.owner = op.active; + trx.operations = {op}; + PUSH_TX( db, trx, ~0 ); + } + + // Bob sets Alice to his active and owner authority + { + account_update_operation op; + op.account = bob_id; + op.active = authority(1, alice_id, 1); + op.owner = op.active; + trx.operations = {op}; + PUSH_TX( db, trx, ~0 ); + } + + BOOST_REQUIRE( bob_id(db).stable_owner.valid() ); + + // Bob can't update his authority (can't sign any operation) + { + account_update_operation op; + op.account = bob_id; + op.active = authority(123, bob_public_key, 123); + op.owner = op.active; + trx.operations = {op}; + sign(trx, bob_private_key); + sign(trx, alice_private_key); + + GRAPHENE_CHECK_THROW( + PUSH_TX( db, trx ), + graphene::chain::tx_missing_owner_auth ); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( unlock_cycled_account_test ) +{ + try { + INVOKE(make_recursive_authority_test); + + GET_ACTOR(bob); + GET_ACTOR(alice); + fund(bob); + + generate_blocks(HARDFORK_CYCLED_ACCOUNTS_TIME, true); + + // unlock + { + account_unlock_operation op; + op.account_to_unlock = bob_id; + op.previous_authority = *bob_id(db).stable_owner; + + trx.operations = {op}; + trx.clear_signatures(); + set_expiration(db, trx); + sign(trx, bob_private_key); + + PUSH_TX( db, trx ); + } + + // account unlocked and can transfer money + auto initial_balance_bob = get_balance(bob_id, asset_id_type()); + auto initial_balance_alice = get_balance(alice_id, asset_id_type()); + + { + transfer_operation t_op; + t_op.from = bob_id; + t_op.to = alice_id; + t_op.amount = asset(1000); + + trx.clear_signatures(); + trx.operations = {t_op}; + sign(trx, bob_private_key); + + PUSH_TX( db, trx ); + } + + BOOST_CHECK_EQUAL( get_balance(bob_id, asset_id_type()), initial_balance_bob - 1000 ); + BOOST_CHECK_EQUAL( get_balance(alice_id, asset_id_type()), initial_balance_alice + 1000 ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( unlock_operation_penalty_payment_test ) +{ + try + { + INVOKE(make_recursive_authority_test); + + GET_ACTOR(bob); + fund(bob); + ACTOR(issuer); + + const auto usd = create_user_issued_asset("USD"); + + issue_uia(issuer, usd.amount(2000)); + transfer(issuer, bob, usd.amount(200)); + transfer(issuer, GRAPHENE_COMMITTEE_ACCOUNT(db), usd.amount(200)); + + auto initial_balance_bob_BTS = get_balance(bob_id, asset_id_type()); + auto initial_balance_bob_USD = get_balance(bob_id, usd.get_id()); + + auto initial_balance_committee_BTS = get_balance( GRAPHENE_COMMITTEE_ACCOUNT, asset_id_type()); + auto initial_balance_committee_USD = get_balance( GRAPHENE_COMMITTEE_ACCOUNT, usd.get_id()); + + generate_blocks(HARDFORK_CYCLED_ACCOUNTS_TIME); + // unlock + { + account_unlock_operation op; + op.account_to_unlock = bob_id; + op.previous_authority = *bob_id(db).stable_owner; + + trx.operations = {op}; + trx.clear_signatures(); + set_expiration(db, trx); + sign(trx, bob_private_key); + + PUSH_TX( db, trx ); + } + + BOOST_CHECK_EQUAL( get_balance(bob_id, asset_id_type()), initial_balance_bob_BTS * 0.9); + BOOST_CHECK_EQUAL( get_balance(bob_id, usd.get_id()), initial_balance_bob_USD * 0.9); + + BOOST_CHECK_EQUAL( get_balance(GRAPHENE_COMMITTEE_ACCOUNT, asset_id_type()), initial_balance_committee_BTS + initial_balance_bob_BTS * 0.1); + BOOST_CHECK_EQUAL( get_balance(GRAPHENE_COMMITTEE_ACCOUNT, usd.get_id()), initial_balance_committee_USD + initial_balance_bob_USD * 0.1); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(unlock_account_operation_failed_not_enough_BTS) +{ + try + { + INVOKE(make_recursive_authority_test); + + generate_blocks(HARDFORK_CYCLED_ACCOUNTS_TIME); + generate_block(); + + GET_ACTOR(bob); + enable_fees(); + + // unlock + account_unlock_operation op; + op.account_to_unlock = bob_id; + op.previous_authority = *bob_id(db).stable_owner; + + trx.operations = {op}; + trx.clear_signatures(); + set_expiration(db, trx); + sign(trx, bob_private_key); + + GRAPHENE_CHECK_THROW( PUSH_TX( db, trx ), fc::exception ); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( cant_execute_unlock_operation_before_HARDFORK_CYCLED_ACCOUNTS_TIME_test ) +{ + try { + ACTORS( (alice) ); + + account_unlock_operation op; + op.account_to_unlock = alice_id; + auto auth = authority(111, alice_public_key, 111); + op.previous_authority = auth; + trx.operations = {op}; + sign(trx, alice_private_key); + + GRAPHENE_CHECK_THROW( + PUSH_TX( db, trx ), + fc::assert_exception ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( cant_unlock_not_locked_account_test ) +{ + try { + ACTORS( (alice) ); + + generate_blocks(HARDFORK_CYCLED_ACCOUNTS_TIME, true); + + account_unlock_operation op; + op.account_to_unlock = alice_id; + auto auth = authority(111, alice_public_key, 111); + op.previous_authority = auth; + trx.operations = {op}; + sign(trx, alice_private_key); + + GRAPHENE_CHECK_THROW( + PUSH_TX( db, trx ), + fc::assert_exception ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( create_account_with_cycled_authority_before_HARDFORK_CYCLED_ACCOUNTS_TIME_test ) +{ + try { + ACTORS( (bob) (alice) ); + + auto update_auth = [&](const account_update_operation& op){ + trx.operations.clear(); + trx.operations.push_back(op); + PUSH_TX( db, trx, ~0 ); + }; + + account_update_operation op; + + op.account = bob_id; + op.active = authority(1, alice_id, 1); + op.owner = op.active; + update_auth(op); + + op.account = alice_id; + op.active = authority(1, bob_id, 1); + op.owner = op.active; + update_auth(op); + + trx.operations.clear(); + account_create_operation create_op = make_account("allowed"); + create_op.owner = authority(1, bob_id, 1); + create_op.active = authority(1, alice_id, 1); + trx.operations.push_back(create_op); + PUSH_TX( db, trx, ~0 ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( create_account_with_cycled_authority_after_HARDFORK_CYCLED_ACCOUNTS_TIME_test ) +{ + try { + INVOKE(create_account_with_cycled_authority_before_HARDFORK_CYCLED_ACCOUNTS_TIME_test); + GET_ACTOR(bob); + GET_ACTOR(alice); + + generate_blocks(HARDFORK_CYCLED_ACCOUNTS_TIME); + set_expiration( db, trx ); + + trx.operations.clear(); + account_create_operation create_op = make_account("disabled"); + create_op.owner = authority(1, bob_id, 1); + create_op.active = authority(1, alice_id, 1); + trx.operations.push_back(create_op); + + GRAPHENE_CHECK_THROW( + PUSH_TX( db, trx, ~0 ), + graphene::chain::tx_missing_active_auth ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( cant_unlock_account_that_created_locked_test ) +{ + try { + INVOKE(create_account_with_cycled_authority_before_HARDFORK_CYCLED_ACCOUNTS_TIME_test); + + generate_blocks(HARDFORK_CYCLED_ACCOUNTS_TIME); + set_expiration( db, trx ); + + const auto &locked_account = get_account("allowed"); + auto priv = generate_private_key("allowed"); + auto pub = public_key_type(priv.get_public_key()); + + account_unlock_operation op; + op.account_to_unlock = locked_account.get_id(); + auto auth = authority(111, pub, 111); + op.previous_authority = auth; + trx.operations = {op}; + sign(trx, priv); + + GRAPHENE_CHECK_THROW( + PUSH_TX( db, trx ), + fc::assert_exception ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( update_account_self_cycled_authority_before_HARDFORK_CYCLED_ACCOUNTS_TIME_test ) +{ + try { + ACTOR(nathan); + + trx.operations.clear(); + + account_update_operation op; + op.account = nathan_id; + op.owner = authority(1, nathan_id, 1); + op.active = authority(1, nathan_id, 1); + trx.operations.push_back(op); + PUSH_TX( db, trx, ~0 ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( update_account_self_cycled_authority_after_HARDFORK_CYCLED_ACCOUNTS_TIME_test ) +{ + try { + ACTOR(alice); + ACTOR(bob); + + fund(bob); + fund(alice); + + generate_blocks(HARDFORK_CYCLED_ACCOUNTS_TIME); + trx.clear(); + set_expiration( db, trx ); + + auto fill_transfer = [&](const account_object& from, + const account_object& to, + const asset& amount, + const asset& fee = asset()) + { + transfer_operation trans; + trans.from = from.id; + trans.to = to.id; + trans.amount = amount; + trx.operations.push_back(trans); + + if( fee == asset() ) + { + for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); + } + trx.validate(); + }; + + // make cycle to self in active and owner authority for Alice + account_update_operation auo; + auo.account = alice_id; + auo.owner = authority(1, alice_id, 1); + auo.active = authority(1, alice_id, 1); + trx.operations.push_back( auo ); + sign( trx, alice_private_key ); + GRAPHENE_CHECK_THROW(PUSH_TX( db, trx ), fc::exception); + trx.clear(); + + // Add Alice to Bob's authority + auo.account = bob_id; + auo.active = authority( 1, alice_id, 1 ); + auo.owner = authority( 1, alice_id, 1 ); + trx.operations.push_back( auo ); + sign( trx, bob_private_key ); + PUSH_TX( db, trx ); + trx.clear(); + + // make cycle to self in active and owner authority via Bob for Alice + auo.account = alice_id; + auo.owner = authority(1, bob_id, 1); + auo.active = authority(1, bob_id, 1); + trx.operations.push_back( auo ); + sign( trx, alice_private_key ); + GRAPHENE_CHECK_THROW(PUSH_TX( db, trx ), fc::exception); + trx.clear(); + + fill_transfer(alice, bob, asset(10)); + sign( trx, bob_private_key ); + sign( trx, alice_private_key ); + GRAPHENE_CHECK_THROW(PUSH_TX( db, trx ), fc::exception); + trx.clear(); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( update_account_authority_test ) +{ + try { + ACTORS( (nathan) (bob) (alice) (jill) (izzy) ); + + auto update_auth = [&](const account_update_operation& op){ + trx.operations.clear(); + trx.operations.push_back(op); + PUSH_TX( db, trx, ~0 ); + }; + + // make cycle with other accounts in owner auth + // | | | + // | account | authority | + // |___________|_________________| + // | bob | alice, jill | + // | alice | jill, nathan | + // | jill | izzy | + // | izzy | nathan | + // |___________|_________________| + + // bob---->alice-----------------| + // | | | + // | |->jill |->nathan<-| + // | |->izzy + + account_update_operation op; + { + op.account = bob_id; + op.active = authority(2, alice_id, 1, jill_id, 1); + op.owner = op.active; + update_auth(op); + { + GET_ACTOR(bob); + BOOST_CHECK(bob.active == op.active); + } + + op.account = alice_id; + op.active = authority(2, jill_id, 1, nathan_id, 1); + op.owner = op.active; + update_auth(op); + { + GET_ACTOR(alice); + BOOST_CHECK(alice.active == op.active); + } + + op.account = jill_id; + op.active = authority(1, izzy_id, 1); + op.owner = op.active; + update_auth(op); + { + GET_ACTOR(jill); + BOOST_CHECK(jill.active == op.active); + } + + op.account = izzy_id; + op.active = authority(1, nathan_id, 1); + op.owner = op.active; + update_auth(op); + { + GET_ACTOR(izzy); + BOOST_CHECK(izzy.active == op.active); + } + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( disables_update_to_empty_account_authority ) +{ + try { + ACTOR(nathan); + + account_update_operation op; + op.account = nathan_id; + op.active = authority{}; + op.owner = op.active; + trx.operations.clear(); + trx.operations.push_back(op); + GRAPHENE_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( update_account_make_cycled_authority_before_HARDFORK_CYCLED_ACCOUNTS_TIME_test) +{ + // make cycle with other accounts in owner auth + // | | | + // | account | authority | + // |___________|_________________| + // | bob | alice, jill | + // | alice | jill, nathan | + // | jill | izzy | + // | izzy | nathan | + // | nathan | bob | + // nathan wonts to add bob to auth + // bob---->alice-----------------| + // | | | + // | |->jill |->nathan<-| + // | |->izzy | + // | | + // |<---------------------| lead to cycle!!! + + try { + INVOKE(update_account_authority_test); + GET_ACTOR(nathan); + GET_ACTOR(bob); + + account_update_operation op; + op.account = nathan_id; + op.active = authority(1, bob_id, 1); + + trx.operations.clear(); + trx.operations.push_back(op); + PUSH_TX( db, trx, ~0 ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( update_account_make_cycled_authority_after_HARDFORK_CYCLED_ACCOUNTS_TIME_test) +{ + // make cycle with other accounts in owner auth + // | | | + // | account | authority | + // |___________|_________________| + // | bob | alice, jill | + // | alice | jill, nathan | + // | jill | izzy | + // | izzy | nathan | + // | nathan | bob | + // nathan wonts to add bob to auth + // bob---->alice-----------------| + // | | | + // | |->jill |->nathan<-| + // | |->izzy | + // | | + // |<---------------------| lead to cycle!!! + + try { + INVOKE(update_account_authority_test); + generate_blocks(HARDFORK_CYCLED_ACCOUNTS_TIME); + set_expiration( db, trx ); + + GET_ACTOR(nathan); + GET_ACTOR(bob); + + account_update_operation op; + op.account = nathan_id; + op.active = authority(1, bob_id, 1); + op.owner = authority(1, bob_id, 1); + + trx.operations.clear(); + trx.operations.push_back(op); + + GRAPHENE_CHECK_THROW( + PUSH_TX( db, trx, ~0 ), + graphene::chain::tx_missing_active_auth); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( update_account_max_authority_depth_before_HARDFORK_CYCLED_ACCOUNTS_TIME_test) +{ + try { + INVOKE(update_account_authority_test); + + GET_ACTOR(nathan); + GET_ACTOR(bob); + + db.modify( db.get_global_properties(), [&]( global_property_object& gpo ) { + gpo.parameters.max_authority_depth = 1; + }); + + account_update_operation op; + op.account = nathan_id; + op.active = authority(1, bob_id, 1); + + trx.operations.clear(); + trx.operations.push_back(op); + PUSH_TX( db, trx, ~0 ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( update_account_max_authority_depth_after_HARDFORK_CYCLED_ACCOUNTS_TIME_test) +{ + try { + INVOKE(update_account_authority_test); + + GET_ACTOR(nathan); + GET_ACTOR(bob); + + generate_blocks(HARDFORK_CYCLED_ACCOUNTS_TIME); + set_expiration( db, trx ); + + db.modify( db.get_global_properties(), [&]( global_property_object& gpo ) { + gpo.parameters.max_authority_depth = 1; + }); + + account_update_operation op; + op.account = nathan_id; + op.active = authority(1, bob_id, 1); + op.owner = op.active; + + trx.operations.clear(); + trx.operations.push_back(op); + + GRAPHENE_CHECK_THROW( + PUSH_TX( db, trx, ~0 ), + graphene::chain::tx_missing_active_auth ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( update_account_exceed_max_authority_depth_after_HARDFORK_CYCLED_ACCOUNTS_TIME_test ) +{ + // make account authority with exceeded depth limit + // this special case possible despite prevention logic defined after HARDFORK_CYCLED_ACCOUNTS_TIME + // + // initial accounts: alice, bob, charlie, dan + // step by step delegate authorities + // 1) alice--->bob + // 2) bob--->charlie + // 3) charlie--->dan + // account alice locked due to exceeded authority depth limit!!! + + try { + ACTORS((alice)(bob)(charlie)(dan)) + + fund(alice); + fund(bob); + fund(charlie); + + generate_blocks(HARDFORK_CYCLED_ACCOUNTS_TIME); + trx.clear(); + set_expiration( db, trx ); + + auto delegate_authorities = [&](const account_id_type& from, + const private_key_type& from_key, + const account_id_type& to) + { + account_update_operation auo; + auo.account = from; + auo.active = authority( 1, to, 1 ); + auo.owner = authority( 1, to, 1 ); + trx.operations.push_back( auo ); + sign( trx, from_key ); + PUSH_TX( db, trx ); + trx.clear(); + }; + + delegate_authorities(alice_id, alice_private_key, bob_id); + delegate_authorities(bob_id, bob_private_key, charlie_id); + delegate_authorities(charlie_id, charlie_private_key, dan_id); + + { + transfer_operation tr_op; + tr_op.from = alice_id; + tr_op.to = dan_id; + tr_op.amount = asset(10); + + trx.operations = { tr_op }; + sign( trx, dan_private_key ); + trx.validate(); + + GRAPHENE_CHECK_THROW(PUSH_TX( db, trx ), graphene::chain::tx_missing_active_auth); + trx.clear(); + } + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( cant_create_auth_chain_longer_than_max_authority_depth_limit_after_HARDFORK_CYCLED_ACCOUNTS_TIME_test ) +{ + // + // initial accounts: alice, bob, charlie, dan + // step by step delegate authorities + // 1) charlie--->dan + // 2) bob--->charlie + // 3) alice--->bob + // alice can't delegete auth to bob due to exceeded authority depth limit!!! + + try { + ACTORS((alice)(bob)(charlie)(dan)) + + fund(alice); + fund(bob); + fund(charlie); + + generate_blocks(HARDFORK_CYCLED_ACCOUNTS_TIME); + trx.clear(); + set_expiration( db, trx ); + + auto delegate_authorities = [&](const account_id_type& from, + const private_key_type& from_key, + const account_id_type& to) + { + account_update_operation auo; + auo.account = from; + auo.active = authority( 1, to, 1 ); + auo.owner = authority( 1, to, 1 ); + trx.operations.push_back( auo ); + sign( trx, from_key ); + PUSH_TX( db, trx ); + trx.clear(); + }; + + delegate_authorities(charlie_id, charlie_private_key, dan_id); + delegate_authorities(bob_id, bob_private_key, charlie_id); + + GRAPHENE_CHECK_THROW( + delegate_authorities(alice_id, alice_private_key, bob_id), + graphene::chain::tx_missing_active_auth); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( cant_create_proposal_with_unlock_account_before_HARDFORK_CYCLED_ACCOUNTS_TIME_test ) +{ + try { + ACTORS( (alice)(bob) ) + + proposal_create_operation pco; + pco.expiration_time = db.head_block_time() + fc::minutes(1); + pco.fee_paying_account = alice_id; + + account_unlock_operation op; + op.account_to_unlock = bob_id; + op.previous_authority = authority(111, bob_public_key, 111); + + pco.proposed_ops.emplace_back( op ); + trx.operations = {pco}; + + GRAPHENE_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::assert_exception ); + } + FC_LOG_AND_RETHROW() +} + BOOST_AUTO_TEST_CASE( transfer_core_asset ) { try {