diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 5f8772d1..74b7d2b5 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -167,3 +167,6 @@ /// Sentinel value used in the scheduler. #define GRAPHENE_NULL_WITNESS (graphene::chain::witness_id_type(0)) ///@} + +// hack for unit test +#define GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET (asset_id_type(1)) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 7a9e7ce4..44cd64da 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -195,6 +196,8 @@ void database_fixture::verify_asset_supplies( const database& db ) } for( const vesting_balance_object& vbo : db.get_index_type< vesting_balance_index >().indices() ) total_balances[ vbo.balance.asset_id ] += vbo.balance.amount; + for( const fba_accumulator_object& fba : db.get_index_type< simple_index< fba_accumulator_object > >() ) + total_balances[ asset_id_type() ] += fba.accumulated_fba_fees; total_balances[asset_id_type()] += db.get_dynamic_global_properties().witness_budget; @@ -1033,6 +1036,24 @@ int64_t database_fixture::get_balance( const account_object& account, const asse return db.get_balance(account.get_id(), a.get_id()).amount.value; } +vector< operation_history_object > database_fixture::get_operation_history( account_id_type account_id )const +{ + vector< operation_history_object > result; + const auto& stats = account_id(db).statistics(db); + if(stats.most_recent_op == account_transaction_history_id_type()) + return result; + + const account_transaction_history_object* node = &stats.most_recent_op(db); + while( true ) + { + result.push_back( node->operation_id(db) ); + if(node->next == account_transaction_history_id_type()) + break; + node = db.find(node->next); + } + return result; +} + namespace test { void set_expiration( const database& db, transaction& tx ) diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 0cb9198d..241fd079 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -28,6 +28,8 @@ #include #include +#include + #include using namespace graphene::db; @@ -278,6 +280,7 @@ struct database_fixture { void print_joint_market( const string& syma, const string& symb )const; int64_t get_balance( account_id_type account, asset_id_type a )const; int64_t get_balance( const account_object& account, const asset_object& a )const; + vector< operation_history_object > get_operation_history( account_id_type account_id )const; }; namespace test { diff --git a/tests/tests/fee_tests.cpp b/tests/tests/fee_tests.cpp index 3e5e191a..9d7f0281 100644 --- a/tests/tests/fee_tests.cpp +++ b/tests/tests/fee_tests.cpp @@ -24,7 +24,12 @@ #include #include + #include + +#include + +#include #include #include @@ -721,4 +726,210 @@ BOOST_AUTO_TEST_CASE( fee_refund_test ) FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE( stealth_fba_test ) +{ + try + { + ACTORS( (alice)(bob)(chloe)(dan)(izzy)(philbin)(tom) ); + upgrade_to_lifetime_member(philbin_id); + + generate_blocks( HARDFORK_538_TIME ); + generate_blocks( HARDFORK_555_TIME ); + generate_blocks( HARDFORK_563_TIME ); + generate_blocks( HARDFORK_572_TIME ); + + // Philbin (registrar who registers Rex) + + // Izzy (initial issuer of stealth asset, will later transfer to Tom) + // Alice, Bob, Chloe, Dan (ABCD) + // Rex (recycler -- buyback account for stealth asset) + // Tom (owner of stealth asset who will be set as top_n authority) + + // Izzy creates STEALTH + asset_id_type stealth_id = create_user_issued_asset( "STEALTH", izzy_id(db), + disable_confidential | transfer_restricted | override_authority | white_list | charge_market_fee ).id; + + /* + // this is disabled because it doesn't work, our modify() is probably being overwritten by undo + + // + // Init blockchain with stealth ID's + // On a real chain, this would be done with #define GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET + // causing the designated_asset fields of these objects to be set at genesis, but for + // this test we modify the db directly. + // + auto set_fba_asset = [&]( uint64_t fba_acc_id, asset_id_type asset_id ) + { + db.modify( fba_accumulator_id_type(fba_acc_id)(db), [&]( fba_accumulator_object& fba ) + { + fba.designated_asset = asset_id; + } ); + }; + + set_fba_asset( fba_accumulator_id_transfer_to_blind , stealth_id ); + set_fba_asset( fba_accumulator_id_blind_transfer , stealth_id ); + set_fba_asset( fba_accumulator_id_transfer_from_blind, stealth_id ); + */ + + // Izzy kills some permission bits (this somehow happened to the real STEALTH in production) + { + asset_update_operation update_op; + update_op.issuer = izzy_id; + update_op.asset_to_update = stealth_id; + asset_options new_options; + new_options = stealth_id(db).options; + new_options.issuer_permissions = charge_market_fee; + new_options.flags = disable_confidential | transfer_restricted | override_authority | white_list | charge_market_fee; + // after fixing #579 you should be able to delete the following line + new_options.core_exchange_rate = price( asset( 1, stealth_id ), asset( 1, asset_id_type() ) ); + update_op.new_options = new_options; + signed_transaction tx; + tx.operations.push_back( update_op ); + set_expiration( db, tx ); + sign( tx, izzy_private_key ); + PUSH_TX( db, tx ); + } + + // Izzy transfers issuer duty to Tom + { + asset_update_operation update_op; + update_op.issuer = izzy_id; + update_op.asset_to_update = stealth_id; + update_op.new_issuer = tom_id; + // new_options should be optional, but isn't...the following line should be unnecessary #580 + update_op.new_options = stealth_id(db).options; + signed_transaction tx; + tx.operations.push_back( update_op ); + set_expiration( db, tx ); + sign( tx, izzy_private_key ); + PUSH_TX( db, tx ); + } + + // Tom re-enables the permission bits to clear the flags, then clears them again + // Allowed by #572 when current_supply == 0 + { + asset_update_operation update_op; + update_op.issuer = tom_id; + update_op.asset_to_update = stealth_id; + asset_options new_options; + new_options = stealth_id(db).options; + new_options.issuer_permissions = new_options.flags | charge_market_fee; + update_op.new_options = new_options; + signed_transaction tx; + // enable perms is one op + tx.operations.push_back( update_op ); + + new_options.issuer_permissions = charge_market_fee; + new_options.flags = charge_market_fee; + update_op.new_options = new_options; + // reset wrongly set flags and reset permissions can be done in a single op + tx.operations.push_back( update_op ); + + set_expiration( db, tx ); + sign( tx, tom_private_key ); + PUSH_TX( db, tx ); + } + + // Philbin registers Rex who will be the asset's buyback, including sig from the new issuer (Tom) + account_id_type rex_id; + { + buyback_account_options bbo; + bbo.asset_to_buy = stealth_id; + bbo.asset_to_buy_issuer = tom_id; + bbo.markets.emplace( asset_id_type() ); + account_create_operation create_op = make_account( "rex" ); + create_op.registrar = philbin_id; + create_op.extensions.value.buyback_options = bbo; + create_op.owner = authority::null_authority(); + create_op.active = authority::null_authority(); + + signed_transaction tx; + tx.operations.push_back( create_op ); + set_expiration( db, tx ); + sign( tx, philbin_private_key ); + sign( tx, tom_private_key ); + + processed_transaction ptx = PUSH_TX( db, tx ); + rex_id = ptx.operation_results.back().get< object_id_type >(); + } + + // Tom issues some asset to Alice and Bob + set_expiration( db, trx ); // #11 + issue_uia( alice_id, asset( 1000, stealth_id ) ); + issue_uia( bob_id, asset( 1000, stealth_id ) ); + + // Tom sets his authority to the top_n of the asset + { + top_holders_special_authority top2; + top2.num_top_holders = 2; + top2.asset = stealth_id; + + account_update_operation op; + op.account = tom_id; + op.extensions.value.active_special_authority = top2; + op.extensions.value.owner_special_authority = top2; + + signed_transaction tx; + tx.operations.push_back( op ); + + set_expiration( db, tx ); + sign( tx, tom_private_key ); + + PUSH_TX( db, tx ); + } + + // Wait until the next maintenance interval for top_n to take effect + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + // Do a blind op to add some fees to the pool. + fund( chloe_id(db), asset( 100000, asset_id_type() ) ); + + auto create_transfer_to_blind = [&]( account_id_type account, asset amount, const std::string& key ) -> transfer_to_blind_operation + { + fc::ecc::private_key blind_key = fc::ecc::private_key::regenerate( fc::sha256::hash( key+"-privkey" ) ); + public_key_type blind_pub = blind_key.get_public_key(); + + fc::sha256 secret = fc::sha256::hash( key+"-secret" ); + fc::sha256 nonce = fc::sha256::hash( key+"-nonce" ); + + transfer_to_blind_operation op; + blind_output blind_out; + blind_out.owner = authority( 1, blind_pub, 1 ); + blind_out.commitment = fc::ecc::blind( secret, amount.amount.value ); + blind_out.range_proof = fc::ecc::range_proof_sign( 0, blind_out.commitment, secret, nonce, 0, 0, amount.amount.value ); + + op.amount = amount; + op.from = account; + op.blinding_factor = fc::ecc::blind_sum( {secret}, 1 ); + op.outputs = {blind_out}; + + return op; + }; + + { + transfer_to_blind_operation op = create_transfer_to_blind( chloe_id, asset( 5000, asset_id_type() ), "chloe-key" ); + op.fee = asset( 1000, asset_id_type() ); + + signed_transaction tx; + tx.operations.push_back( op ); + set_expiration( db, tx ); + sign( tx, chloe_private_key ); + + PUSH_TX( db, tx ); + } + + // wait until next maint interval + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + idump( ( get_operation_history( chloe_id ) ) ); + idump( ( get_operation_history( rex_id ) ) ); + idump( ( get_operation_history( tom_id ) ) ); + } + catch( const fc::exception& e ) + { + elog( "caught exception ${e}", ("e", e.to_detail_string()) ); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END()