diff --git a/.github/workflows/build-and-test.mac.yml b/.github/workflows/build-and-test.mac.yml index edc02f659c..bf43b5baae 100644 --- a/.github/workflows/build-and-test.mac.yml +++ b/.github/workflows/build-and-test.mac.yml @@ -42,9 +42,10 @@ jobs: run: | export CCACHE_DIR="$GITHUB_WORKSPACE/ccache" mkdir -p "$CCACHE_DIR" - make -j 2 -C _build witness_node cli_wallet app_test cli_test chain_test + make -j 3 -C _build witness_node cli_wallet app_test cli_test chain_test df -h - name: Unit-Tests + timeout-minutes: 15 run: | _build/tests/app_test -l test_suite libraries/fc/tests/run-parallel-tests.sh _build/tests/chain_test -l test_suite diff --git a/.github/workflows/build-and-test.ubuntu-debug.yml b/.github/workflows/build-and-test.ubuntu-debug.yml index 9c626766ca..b026557ea3 100644 --- a/.github/workflows/build-and-test.ubuntu-debug.yml +++ b/.github/workflows/build-and-test.ubuntu-debug.yml @@ -8,12 +8,26 @@ jobs: name: Build and test in Debug mode strategy: matrix: - os: [ ubuntu-18.04, ubuntu-20.04 ] + os: [ ubuntu-20.04 ] runs-on: ${{ matrix.os }} services: - elasticsearch: - image: docker://elasticsearch:7.17.7 - options: --env discovery.type=single-node --publish 9200:9200 --publish 9300:9300 + elasticsearch8: + image: elastic/elasticsearch:8.10.2 + options: >- + --env ES_JAVA_OPTS="-Xms512m -Xmx512m" + --env discovery.type=single-node + --env xpack.security.enabled=false + --env xpack.security.http.ssl.enabled=false + --env action.destructive_requires_name=false + --env cluster.routing.allocation.disk.threshold_enabled=false + --publish 9200:9200 + elasticsearch7: + image: elastic/elasticsearch:7.17.13 + options: >- + --env ES_JAVA_OPTS="-Xms512m -Xmx512m" + --env discovery.type=single-node + --env cluster.routing.allocation.disk.threshold_enabled=false + --publish 9201:9200 steps: - name: Install dependencies run: | @@ -51,20 +65,15 @@ jobs: pwd df -h . free - sudo dd if=/dev/zero of=/swapfile bs=1024 count=4M - sudo chmod 600 /swapfile - sudo mkswap /swapfile - sudo swapon /swapfile - free mkdir -p _build - sudo mkdir -p /_build/libraries /_build/programs /_build/tests /mnt/_build - sudo chmod a+rwx /_build/libraries /_build/programs /_build/tests + sudo mkdir -p /_build/libraries /_build/programs /mnt/_build/tests + sudo chmod a+rwx /_build/libraries /_build/programs /mnt/_build/tests ln -s /_build/libraries _build/libraries ln -s /_build/programs _build/programs - ln -s /_build/tests _build/tests + ln -s /mnt/_build/tests _build/tests sudo ln -s /_build/libraries /mnt/_build/libraries sudo ln -s /_build/programs /mnt/_build/programs - sudo ln -s /_build/tests /mnt/_build/tests + sudo ln -s /mnt/_build/tests /_build/tests ls -al _build pushd _build export -n BOOST_ROOT BOOST_INCLUDEDIR BOOST_LIBRARYDIR @@ -89,25 +98,25 @@ jobs: export CCACHE_DIR="$GITHUB_WORKSPACE/ccache" mkdir -p "$CCACHE_DIR" df -h - make -j 1 -C _build chain_test - make -j 1 -C _build cli_test - make -j 1 -C _build app_test - make -j 1 -C _build es_test - make -j 1 -C _build cli_wallet - make -j 1 -C _build witness_node - make -j 1 -C _build + make -j 2 -C _build chain_test + make -j 2 -C _build cli_test + make -j 2 -C _build app_test + make -j 2 -C _build es_test + make -j 2 -C _build cli_wallet + make -j 2 -C _build witness_node + make -j 2 -C _build df -h du -hs _build/libraries/* _build/programs/* _build/tests/* du -hs _build/* du -hs /_build/* - name: Unit-Tests + timeout-minutes: 15 run: | _build/tests/app_test -l test_suite df -h rm -rf /tmp/graphene* - curl -XPUT -H "Content-Type: application/json" http://localhost:9200/_cluster/settings \ - -d '{ "transient": { "cluster.routing.allocation.disk.threshold_enabled": false } }' - echo + _build/tests/es_test -l test_suite + export GRAPHENE_TESTING_ES_URL=http://127.0.0.1:9201/ _build/tests/es_test -l test_suite df -h rm -rf /tmp/graphene* diff --git a/.github/workflows/build-and-test.ubuntu-release.yml b/.github/workflows/build-and-test.ubuntu-release.yml index a2bd68ebe9..9fc718e852 100644 --- a/.github/workflows/build-and-test.ubuntu-release.yml +++ b/.github/workflows/build-and-test.ubuntu-release.yml @@ -8,12 +8,26 @@ jobs: name: Build and test in Release mode strategy: matrix: - os: [ ubuntu-18.04, ubuntu-20.04 ] + os: [ ubuntu-20.04 ] runs-on: ${{ matrix.os }} services: - elasticsearch: - image: docker://elasticsearch:7.17.7 - options: --env discovery.type=single-node --publish 9200:9200 --publish 9300:9300 + elasticsearch8: + image: elastic/elasticsearch:8.10.2 + options: >- + --env ES_JAVA_OPTS="-Xms512m -Xmx512m" + --env discovery.type=single-node + --env xpack.security.enabled=false + --env xpack.security.http.ssl.enabled=false + --env action.destructive_requires_name=false + --env cluster.routing.allocation.disk.threshold_enabled=false + --publish 9200:9200 + elasticsearch7: + image: elastic/elasticsearch:7.17.13 + options: >- + --env ES_JAVA_OPTS="-Xms512m -Xmx512m" + --env discovery.type=single-node + --env cluster.routing.allocation.disk.threshold_enabled=false + --publish 9201:9200 steps: - name: Install dependencies run: | @@ -45,6 +59,8 @@ jobs: submodules: recursive - name: Configure run: | + df -h + free mkdir -p _build pushd _build export -n BOOST_ROOT BOOST_INCLUDEDIR BOOST_LIBRARYDIR @@ -68,14 +84,14 @@ jobs: run: | export CCACHE_DIR="$GITHUB_WORKSPACE/ccache" mkdir -p "$CCACHE_DIR" - make -j 1 -C _build + make -j 2 -C _build df -h - name: Unit-Tests + timeout-minutes: 15 run: | _build/tests/app_test -l test_suite - curl -XPUT -H "Content-Type: application/json" http://localhost:9200/_cluster/settings \ - -d '{ "transient": { "cluster.routing.allocation.disk.threshold_enabled": false } }' - echo + _build/tests/es_test -l test_suite + export GRAPHENE_TESTING_ES_URL=http://127.0.0.1:9201/ _build/tests/es_test -l test_suite libraries/fc/tests/run-parallel-tests.sh _build/tests/chain_test -l test_suite _build/tests/cli_test -l test_suite diff --git a/.github/workflows/build-and-test.win.yml b/.github/workflows/build-and-test.win.yml index 2419ff10c3..e2f21f64d7 100644 --- a/.github/workflows/build-and-test.win.yml +++ b/.github/workflows/build-and-test.win.yml @@ -6,9 +6,9 @@ env: # The following are for windows cross-build only: BOOST_VERSION: 1_69_0 BOOST_DOTTED_VERSION: 1.69.0 - CURL_VERSION: 7.86.0 - OPENSSL_VERSION: 1.1.1s - ZLIB_VERSION: 1.2.13 + CURL_VERSION: 8.3.0 + OPENSSL_VERSION: 1.1.1w + ZLIB_VERSION: 1.3 jobs: prepare-mingw64-libs: name: Build required 3rd-party libraries @@ -154,4 +154,4 @@ jobs: run: | export CCACHE_DIR="$GITHUB_WORKSPACE/ccache" mkdir -p "$CCACHE_DIR" - make -j 2 -C _build witness_node cli_wallet + make VERBOSE=1 -j 2 -C _build witness_node cli_wallet diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index a1a5d134ef..ef9d249e4a 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -34,7 +34,7 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Build only - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: context: . load: true @@ -46,14 +46,14 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Push to DockerHub (for branches) if: env.DOCKER_PUSH_TAG != '' && env.DOCKER_PUSH_TAG_SHORT == '' - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: context: . push: true tags: ${{ secrets.DOCKERHUB_REPO_PATH }}:${{ env.DOCKER_PUSH_TAG }} - name: Push to DockerHub (for tags) if: env.DOCKER_PUSH_TAG != '' && env.DOCKER_PUSH_TAG_SHORT != '' - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: context: . push: true diff --git a/.github/workflows/sonar-scan.yml b/.github/workflows/sonar-scan.yml index eca0b916ba..9d2dcd2ba4 100644 --- a/.github/workflows/sonar-scan.yml +++ b/.github/workflows/sonar-scan.yml @@ -11,9 +11,16 @@ jobs: os: [ ubuntu-20.04 ] runs-on: ${{ matrix.os }} services: - elasticsearch: - image: docker://elasticsearch:7.17.7 - options: --env discovery.type=single-node --publish 9200:9200 --publish 9300:9300 + elasticsearch8: + image: elastic/elasticsearch:8.10.2 + options: >- + --env ES_JAVA_OPTS="-Xms512m -Xmx512m" + --env discovery.type=single-node + --env xpack.security.enabled=false + --env xpack.security.http.ssl.enabled=false + --env action.destructive_requires_name=false + --env cluster.routing.allocation.disk.threshold_enabled=false + --publish 9200:9200 steps: - name: Download and install latest SonarScanner CLI tool run: | @@ -43,6 +50,7 @@ jobs: sudo apt-get install -y --allow-downgrades openssl=${openssl_ver} libssl-dev=${libssl_ver} sudo apt-get install -y \ ccache \ + gcovr \ parallel \ libboost-thread-dev \ libboost-iostreams-dev \ @@ -70,11 +78,6 @@ jobs: pwd df -h . free - sudo dd if=/dev/zero of=/swapfile bs=1024 count=4M - sudo chmod 600 /swapfile - sudo mkswap /swapfile - sudo swapon /swapfile - free mkdir -p _build sudo mkdir -p /_build/libraries /_build/programs /mnt/_build/tests sudo chmod a+rwx /_build/libraries /_build/programs /mnt/_build/tests @@ -107,7 +110,6 @@ jobs: with: path: | ccache - sonar_cache key: sonar-${{ env.OS_VERSION }}-${{ github.ref }}-${{ github.sha }} restore-keys: | sonar-${{ env.OS_VERSION }}-${{ github.ref }}- @@ -117,7 +119,7 @@ jobs: export CCACHE_DIR="$GITHUB_WORKSPACE/ccache" mkdir -p "$CCACHE_DIR" df -h - programs/build_helpers/make_with_sonar bw-output -j 1 -C _build \ + programs/build_helpers/make_with_sonar bw-output -j 2 -C _build \ witness_node cli_wallet js_operation_serializer get_dev_key network_mapper \ app_test chain_test cli_test es_test df -h @@ -162,14 +164,12 @@ jobs: rm -rf _build/programs/genesis_util/get_dev_key df -h - name: Unit-Tests + timeout-minutes: 15 run: | _build/tests/app_test -l test_suite df -h echo "Cleanup" rm -rf /tmp/graphene* - curl -XPUT -H "Content-Type: application/json" http://localhost:9200/_cluster/settings \ - -d '{ "transient": { "cluster.routing.allocation.disk.threshold_enabled": false } }' - echo _build/tests/es_test -l test_suite df -h echo "Cleanup" @@ -185,24 +185,14 @@ jobs: df -h - name: Prepare for scanning with SonarScanner run: | - mkdir -p sonar_cache - find _build/libraries/[acdenptuw]*/CMakeFiles/*.dir \ - _build/libraries/plugins/*/CMakeFiles/*.dir \ - -type d -print \ - | while read d; do \ - tmpd="${d:7}"; \ - srcd="${tmpd/CMakeFiles*.dir/.}"; \ - gcov -o "$d" "${srcd}"/*.[ch][px][px] \ - "${srcd}"/include/graphene/*/*.[ch][px][px] ; \ - done >/dev/null - find _build/programs/[cdgjsw]*/CMakeFiles/*.dir \ - -type d -print \ - | while read d; do \ - tmpd="${d:7}"; \ - srcd="${tmpd/CMakeFiles*.dir/.}"; \ - gcov -o "$d" "${srcd}"/*.[ch][px][px] ; \ - done >/dev/null programs/build_helpers/set_sonar_branch_for_github_actions sonar-project.properties + pushd _build + gcovr --version + gcovr --exclude-unreachable-branches --exclude-throw-branches \ + --exclude '\.\./programs/' \ + --exclude '\.\./tests/' \ + --sonarqube ../coverage.xml -r .. + popd - name: Scan with SonarScanner env: # to get access to secrets.SONAR_TOKEN, provide GITHUB_TOKEN diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index aaa29e509b..2f8a19d2fe 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -69,6 +69,7 @@ Jose Marcial Vieira Bisneto Jozef Knaperek Ken Code Krzysztof Szumny +Massimo Paladin Paul Brossier Roelandp Semen Martynov @@ -80,5 +81,6 @@ bitcube hammadsherwani <83015346+hammadsherwani@users.noreply.github.com> lafona liondani +litepresence lososeg sinetek diff --git a/Doxyfile b/Doxyfile index 7f596caf93..b876a077d2 100644 --- a/Doxyfile +++ b/Doxyfile @@ -38,13 +38,13 @@ PROJECT_NAME = "BitShares-Core" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = "6.1.0" +PROJECT_NUMBER = "7.0.0" # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = "BitShares blockchain implementation and command-line interface software" +PROJECT_BRIEF = "BitShares blockchain node software and command-line wallet software" # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 diff --git a/LICENSE.txt b/LICENSE.txt index dab0004a09..0b8ebaf47b 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2015-2022 Cryptonomex Inc. and +Copyright (c) 2015-2023 Cryptonomex Inc. and contributors (see CONTRIBUTORS.txt) The MIT License diff --git a/docker/default_config.ini b/docker/default_config.ini index c15db1e715..10fe5a9817 100644 --- a/docker/default_config.ini +++ b/docker/default_config.ini @@ -213,13 +213,10 @@ max-ops-per-account = 100 # ============================================================================== # Track market history by grouping orders into buckets of equal size measured in seconds specified as a JSON array of numbers -# bucket-size = [15,60,300,3600,86400] -bucket-size = [60,300,900,1800,3600,14400,86400] -# for 1 min, 5 mins, 30 mins, 1h, 4 hs and 1 day. i think this should be the default. -# https://github.com/bitshares/bitshares-core/issues/465 +bucket-size = [60,300,900,3600,14400,86400,604800] # How far back in time to track history for each bucket size, measured in the number of buckets (default: 1000) -history-per-size = 1000 +history-per-size = 1500 # Will only store this amount of matched orders for each market in order history for querying, or those meet the other option, which has more data (default: 1000) max-order-his-records-per-market = 1000 diff --git a/docs b/docs index 6f6ea4ef17..8ba17318bd 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 6f6ea4ef17c72bc7d5fbdab5f98d1bea364b8723 +Subproject commit 8ba17318bdb24277906256f7bd4f56fe033fb288 diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 4556d42972..3fd6f92d33 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -59,21 +59,19 @@ namespace graphene { namespace app { return uint32_t(1); // Note: hard code it here for backward compatibility FC_ASSERT( o_user.valid() && o_password.valid(), "Must provide both user and password" ); - string user = *o_user; - optional< api_access_info > acc = _app.get_api_access_info( user ); - if( !acc.valid() ) + optional< api_access_info > acc = _app.get_api_access_info( *o_user ); + if( !acc ) return logout(); if( acc->password_hash_b64 != "*" ) { - std::string password_salt = fc::base64_decode( acc->password_salt_b64 ); std::string acc_password_hash = fc::base64_decode( acc->password_hash_b64 ); - - string password = *o_password; - fc::sha256 hash_obj = fc::sha256::hash( password + password_salt ); - if( hash_obj.data_size() != acc_password_hash.length() ) + if( fc::sha256::data_size() != acc_password_hash.length() ) return logout(); - if( memcmp( hash_obj.data(), acc_password_hash.c_str(), hash_obj.data_size() ) != 0 ) + + std::string password_salt = fc::base64_decode( acc->password_salt_b64 ); + fc::sha256 hash_obj = fc::sha256::hash( *o_password + password_salt ); + if( memcmp( hash_obj.data(), acc_password_hash.data(), fc::sha256::data_size() ) != 0 ) return logout(); } diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index c421ad2a12..b84fb0a16b 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -183,7 +183,7 @@ void application_impl::reset_p2p_node(const fc::path& data_dir) _p2p_network->sync_from(net::item_id(net::core_message_type_enum::block_message_type, _chain_db->head_block_id()), std::vector()); -} FC_CAPTURE_AND_RETHROW() } +} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE void application_impl::new_connection( const fc::http::websocket_connection_ptr& c ) { @@ -234,7 +234,7 @@ void application_impl::reset_websocket_server() ilog("Configured websocket rpc to listen on ${ip}", ("ip",_options->at("rpc-endpoint").as())); _websocket_server->listen( fc::ip::endpoint::from_string(_options->at("rpc-endpoint").as()) ); _websocket_server->start_accept(); -} FC_CAPTURE_AND_RETHROW() } +} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE void application_impl::reset_websocket_tls_server() { try { @@ -259,7 +259,7 @@ void application_impl::reset_websocket_tls_server() ilog("Configured websocket TLS rpc to listen on ${ip}", ("ip",_options->at("rpc-tls-endpoint").as())); _websocket_tls_server->listen( fc::ip::endpoint::from_string(_options->at("rpc-tls-endpoint").as()) ); _websocket_tls_server->start_accept(); -} FC_CAPTURE_AND_RETHROW() } +} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE void application_impl::initialize(const fc::path& data_dir, shared_ptr options) { @@ -526,7 +526,7 @@ graphene::chain::genesis_state_type application_impl::initialize_genesis_state() genesis.initial_chain_id = fc::sha256::hash( egenesis_json ); return genesis; } - } FC_CAPTURE_AND_RETHROW() + } FC_CAPTURE_AND_RETHROW() // GCOVR_EXCL_LINE } void application_impl::open_chain_database() const @@ -634,7 +634,7 @@ bool application_impl::is_plugin_enabled(const string& name) const return !(_active_plugins.find(name) == _active_plugins.end()); } -/** +/* * If delegate has the item, the network has no need to fetch it. */ bool application_impl::has_item(const net::item_id& id) @@ -646,10 +646,10 @@ bool application_impl::has_item(const net::item_id& id) else return _chain_db->is_known_transaction(id.item_hash); } - FC_CAPTURE_AND_RETHROW( (id) ) + FC_CAPTURE_AND_RETHROW( (id) ) // GCOVR_EXCL_LINE } -/** +/* * @brief allows the application to validate an item prior to broadcasting to peers. * * @param sync_mode true if the message was fetched through the sync process, false during normal operation @@ -728,7 +728,7 @@ bool application_impl::handle_block(const graphene::net::block_message& blk_msg, _is_finished_syncing = true; _self.syncing_finished(); } -} FC_CAPTURE_AND_RETHROW( (blk_msg)(sync_mode) ) return false; } +} FC_CAPTURE_AND_RETHROW( (blk_msg)(sync_mode) ) return false; } // GCOVR_EXCL_LINE void application_impl::handle_transaction(const graphene::net::trx_message& transaction_message) { try { @@ -744,7 +744,7 @@ void application_impl::handle_transaction(const graphene::net::trx_message& tran _chain_db->precompute_parallel( transaction_message.trx ).wait(); _chain_db->push_transaction( transaction_message.trx ); -} FC_CAPTURE_AND_RETHROW( (transaction_message) ) } +} FC_CAPTURE_AND_RETHROW( (transaction_message) ) } // GCOVR_EXCL_LINE void application_impl::handle_message(const message& message_to_process) { @@ -759,7 +759,7 @@ bool application_impl::is_included_block(const block_id_type& block_id) return block_id == block_id_in_preferred_chain; } -/** +/* * Assuming all data elements are ordered in some way, this method should * return up to limit ids that occur *after* the last ID in synopsis that * we recognize. @@ -814,9 +814,9 @@ std::vector application_impl::get_block_ids(const std::vectorhead_block_num() - block_header::num_from_id(result.back()); return result; -} FC_CAPTURE_AND_RETHROW( (blockchain_synopsis)(remaining_item_count)(limit) ) } +} FC_CAPTURE_AND_RETHROW( (blockchain_synopsis)(remaining_item_count)(limit) ) } // GCOVR_EXCL_LINE -/** +/* * Given the hash of the requested data, fetch the body. */ message application_impl::get_item(const item_id& id) @@ -833,14 +833,14 @@ message application_impl::get_item(const item_id& id) return block_message(std::move(*opt_block)); } return trx_message( _chain_db->get_recent_transaction( id.item_hash ) ); -} FC_CAPTURE_AND_RETHROW( (id) ) } +} FC_CAPTURE_AND_RETHROW( (id) ) } // GCOVR_EXCL_LINE chain_id_type application_impl::get_chain_id() const { return _chain_db->get_chain_id(); } -/** +/* * Returns a synopsis of the blockchain used for syncing. This consists of a list of * block hashes at intervals exponentially increasing towards the genesis block. * When syncing to a peer, the peer uses this data to determine if we're on the same @@ -1016,9 +1016,9 @@ std::vector application_impl::get_blockchain_synopsis(const item_ha //idump((synopsis)); return synopsis; -} FC_CAPTURE_AND_RETHROW() } +} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE -/** +/* * Call this after the call to handle_message succeeds. * * @param item_type the type of the item we're synchronizing, will be the same as item passed to the sync_from() call @@ -1030,7 +1030,7 @@ void application_impl::sync_status(uint32_t item_type, uint32_t item_count) // any status reports to GUI go here } -/** +/* * Call any time the number of connected peers changes. */ void application_impl::connection_count_changed(uint32_t c) @@ -1041,9 +1041,9 @@ void application_impl::connection_count_changed(uint32_t c) uint32_t application_impl::get_block_number(const item_hash_t& block_id) { try { return block_header::num_from_id(block_id); -} FC_CAPTURE_AND_RETHROW( (block_id) ) } +} FC_CAPTURE_AND_RETHROW( (block_id) ) } // GCOVR_EXCL_LINE -/** +/* * Returns the time a block was produced (if block_id = 0, returns genesis time). * If we don't know about the block, returns time_point_sec::min() */ @@ -1052,7 +1052,7 @@ fc::time_point_sec application_impl::get_block_time(const item_hash_t& block_id) auto opt_block = _chain_db->fetch_block_by_id( block_id ); if( opt_block.valid() ) return opt_block->timestamp; return fc::time_point_sec::min(); -} FC_CAPTURE_AND_RETHROW( (block_id) ) } +} FC_CAPTURE_AND_RETHROW( (block_id) ) } // GCOVR_EXCL_LINE item_hash_t application_impl::get_head_block_id() const { diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 5e23ece291..b3ccff00e6 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -182,7 +182,7 @@ namespace detail { } // graphene::chain::detail -void_result asset_create_evaluator::do_evaluate( const asset_create_operation& op ) +void_result asset_create_evaluator::do_evaluate( const asset_create_operation& op ) const { try { const database& d = db(); @@ -235,11 +235,11 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o if( dotpos != std::string::npos ) { auto prefix = op.symbol.substr( 0, dotpos ); - auto asset_symbol_itr = asset_indx.find( prefix ); - FC_ASSERT( asset_symbol_itr != asset_indx.end(), + auto asset_prefix_itr = asset_indx.find( prefix ); + FC_ASSERT( asset_prefix_itr != asset_indx.end(), "Asset ${s} may only be created by issuer of asset ${p}, but asset ${p} has not been created", ("s",op.symbol)("p",prefix) ); - FC_ASSERT( asset_symbol_itr->issuer == op.issuer, "Asset ${s} may only be created by issuer of ${p}, ${i}", + FC_ASSERT( asset_prefix_itr->issuer == op.issuer, "Asset ${s} may only be created by issuer of ${p}, ${i}", ("s",op.symbol)("p",prefix)("i", op.issuer(d).name) ); } } @@ -269,7 +269,7 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o } return void_result(); -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE void asset_create_evaluator::pay_fee() { @@ -279,7 +279,7 @@ void asset_create_evaluator::pay_fee() generic_evaluator::pay_fee(); } -object_id_type asset_create_evaluator::do_apply( const asset_create_operation& op ) +object_id_type asset_create_evaluator::do_apply( const asset_create_operation& op ) const { try { database& d = db(); @@ -294,7 +294,7 @@ object_id_type asset_create_evaluator::do_apply( const asset_create_operation& o if( fee_is_odd && !hf_429 ) { d.modify( d.get_core_dynamic_data(), []( asset_dynamic_data_object& dd ) { - dd.current_supply++; + ++dd.current_supply; }); } @@ -315,7 +315,7 @@ object_id_type asset_create_evaluator::do_apply( const asset_create_operation& o a.symbol = op.symbol; a.precision = op.precision; a.options = op.common_options; - if( a.options.core_exchange_rate.base.asset_id.instance.value == 0 ) + if( 0 == a.options.core_exchange_rate.base.asset_id.instance.value ) a.options.core_exchange_rate.quote.asset_id = next_asset_id; else a.options.core_exchange_rate.base.asset_id = next_asset_id; @@ -328,7 +328,7 @@ object_id_type asset_create_evaluator::do_apply( const asset_create_operation& o FC_ASSERT( new_asset.id == next_asset_id, "Unexpected object database error, object id mismatch" ); return new_asset.id; -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE void_result asset_issue_evaluator::do_evaluate( const asset_issue_operation& o ) { try { @@ -349,9 +349,9 @@ void_result asset_issue_evaluator::do_evaluate( const asset_issue_operation& o ) FC_ASSERT( (asset_dyn_data->current_supply + o.asset_to_issue.amount) <= a.options.max_supply ); return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE -void_result asset_issue_evaluator::do_apply( const asset_issue_operation& o ) +void_result asset_issue_evaluator::do_apply( const asset_issue_operation& o ) const { try { db().adjust_balance( o.issue_to_account, o.asset_to_issue ); @@ -360,7 +360,7 @@ void_result asset_issue_evaluator::do_apply( const asset_issue_operation& o ) }); return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE void_result asset_reserve_evaluator::do_evaluate( const asset_reserve_operation& o ) { try { @@ -391,9 +391,9 @@ void_result asset_reserve_evaluator::do_evaluate( const asset_reserve_operation& } return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE -void_result asset_reserve_evaluator::do_apply( const asset_reserve_operation& o ) +void_result asset_reserve_evaluator::do_apply( const asset_reserve_operation& o ) const { try { db().adjust_balance( o.payer, -o.amount_to_reserve ); @@ -402,20 +402,20 @@ void_result asset_reserve_evaluator::do_apply( const asset_reserve_operation& o }); return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE void_result asset_fund_fee_pool_evaluator::do_evaluate(const asset_fund_fee_pool_operation& o) { try { - database& d = db(); + const database& d = db(); const asset_object& a = o.asset_id(d); asset_dyn_data = &a.dynamic_asset_data_id(d); return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE -void_result asset_fund_fee_pool_evaluator::do_apply(const asset_fund_fee_pool_operation& o) +void_result asset_fund_fee_pool_evaluator::do_apply(const asset_fund_fee_pool_operation& o) const { try { db().adjust_balance(o.from_account, -o.amount); @@ -424,7 +424,7 @@ void_result asset_fund_fee_pool_evaluator::do_apply(const asset_fund_fee_pool_op }); return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE static void validate_new_issuer( const database& d, const asset_object& a, account_id_type new_issuer ) { try { @@ -441,7 +441,7 @@ static void validate_new_issuer( const database& d, const asset_object& a, accou FC_ASSERT( backing.get_id() == asset_id_type(), "May not create a blockchain-controlled market asset which is not backed by CORE."); } -} FC_CAPTURE_AND_RETHROW( (a)(new_issuer) ) } +} FC_CAPTURE_AND_RETHROW( (a)(new_issuer) ) } // GCOVR_EXCL_LINE void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) { try { @@ -593,7 +593,7 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) d.get(id); return void_result(); -} FC_CAPTURE_AND_RETHROW((o)) } +} FC_CAPTURE_AND_RETHROW((o)) } // GCOVR_EXCL_LINE void_result asset_update_evaluator::do_apply(const asset_update_operation& o) { try { @@ -655,7 +655,7 @@ void_result asset_update_evaluator::do_apply(const asset_update_operation& o) }); return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE void_result asset_update_issuer_evaluator::do_evaluate(const asset_update_issuer_operation& o) { try { @@ -671,7 +671,7 @@ void_result asset_update_issuer_evaluator::do_evaluate(const asset_update_issuer ("o.issuer", o.issuer)("a.issuer", a.issuer) ); return void_result(); -} FC_CAPTURE_AND_RETHROW((o)) } +} FC_CAPTURE_AND_RETHROW((o)) } // GCOVR_EXCL_LINE void_result asset_update_issuer_evaluator::do_apply(const asset_update_issuer_operation& o) { try { @@ -681,7 +681,7 @@ void_result asset_update_issuer_evaluator::do_apply(const asset_update_issuer_op }); return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE /**************** * Loop through assets, looking for ones that are backed by the asset being changed. When found, @@ -743,7 +743,7 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita const asset_bitasset_data_object& current_bitasset_data = asset_obj.bitasset_data(d); if( !HARDFORK_CORE_2282_PASSED( next_maint_time ) ) - FC_ASSERT( !current_bitasset_data.has_settlement(), + FC_ASSERT( !current_bitasset_data.is_globally_settled(), "Cannot update a bitasset after a global settlement has executed" ); if( current_bitasset_data.is_prediction_market ) @@ -784,14 +784,14 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita if( old_bsrm != new_bsrm ) { FC_ASSERT( asset_obj.can_owner_update_bsrm(), "No permission to update BSRM" ); - FC_ASSERT( !current_bitasset_data.has_settlement(), + FC_ASSERT( !current_bitasset_data.is_globally_settled(), "Unable to update BSRM when the asset has been globally settled" ); // Note: it is probably OK to allow BSRM update, be conservative here so far using bsrm_type = bitasset_options::black_swan_response_type; if( bsrm_type::individual_settlement_to_fund == old_bsrm ) - FC_ASSERT( !current_bitasset_data.has_individual_settlement(), - "Unable to update BSRM when the individual settlement pool is not empty" ); + FC_ASSERT( !current_bitasset_data.is_individually_settled_to_fund(), + "Unable to update BSRM when the individual settlement pool (for force-settlements) is not empty" ); else if( bsrm_type::individual_settlement_to_order == old_bsrm ) FC_ASSERT( !d.find_settled_debt_order( op.asset_to_update ), "Unable to update BSRM when there exists an individual settlement order" ); @@ -808,7 +808,7 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita // Are we changing the backing asset? if( op.new_options.short_backing_asset != current_bitasset_data.options.short_backing_asset ) { - FC_ASSERT( !current_bitasset_data.has_settlement(), + FC_ASSERT( !current_bitasset_data.is_globally_settled(), "Cannot change backing asset after a global settlement has executed" ); const asset_dynamic_data_object& dyn = asset_obj.dynamic_asset_data_id(d); @@ -887,7 +887,7 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita asset_to_update = &asset_obj; return void_result(); -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE /******* * @brief Apply requested changes to bitasset options @@ -1042,7 +1042,7 @@ void_result asset_update_bitasset_evaluator::do_apply(const asset_update_bitasse return void_result(); - } FC_CAPTURE_AND_RETHROW( (op) ) + } FC_CAPTURE_AND_RETHROW( (op) ) // GCOVR_EXCL_LINE } void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_update_feed_producers_operation& o) @@ -1067,7 +1067,7 @@ void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_updat d.get(id); return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_feed_producers_operation& o) const { try { @@ -1105,7 +1105,7 @@ void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_f d.check_call_orders( *asset_to_update, true, false, &bitasset_to_update ); return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE void_result asset_global_settle_evaluator::do_evaluate(const asset_global_settle_evaluator::operation_type& op) { try { @@ -1119,7 +1119,7 @@ void_result asset_global_settle_evaluator::do_evaluate(const asset_global_settle const asset_bitasset_data_object& _bitasset_data = asset_to_settle->bitasset_data(d); // if there is a settlement for this asset, then no further global settle may be taken - FC_ASSERT( !_bitasset_data.has_settlement(), + FC_ASSERT( !_bitasset_data.is_globally_settled(), "This asset has been globally settled, cannot globally settle again" ); // Note: after core-2467 hard fork, there can be no debt position due to individual settlements, so we check here @@ -1133,14 +1133,14 @@ void_result asset_global_settle_evaluator::do_evaluate(const asset_global_settle } return void_result(); -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE void_result asset_global_settle_evaluator::do_apply(const asset_global_settle_evaluator::operation_type& op) { try { database& d = db(); d.globally_settle_asset( *asset_to_settle, op.settle_price ); return void_result(); -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::operation_type& op) { try { @@ -1150,14 +1150,14 @@ void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::op "Can only force settle a predition market or a market issued asset" ); const auto& bitasset = asset_to_settle->bitasset_data(d); - FC_ASSERT( asset_to_settle->can_force_settle() || bitasset.has_settlement() - || bitasset.has_individual_settlement(), + FC_ASSERT( asset_to_settle->can_force_settle() || bitasset.is_globally_settled() + || bitasset.is_individually_settled_to_fund(), "Either the asset need to have the force_settle flag enabled, or it need to be globally settled, " - "or the individual settlement pool is not empty" ); + "or the individual settlement pool (for force-settlements) is not empty" ); if( bitasset.is_prediction_market ) { - FC_ASSERT( bitasset.has_settlement(), + FC_ASSERT( bitasset.is_globally_settled(), "Global settlement must occur before force settling a prediction market" ); } else if( bitasset.current_feed.settlement_price.is_null() ) @@ -1169,11 +1169,11 @@ void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::op "Before the core-216 hard fork, unable to force settle when there is no sufficient " " price feeds, no matter if the asset has been globally settled" ); } - if( !bitasset.has_settlement() && !bitasset.has_individual_settlement() ) + if( !bitasset.is_globally_settled() && !bitasset.is_individually_settled_to_fund() ) { FC_THROW_EXCEPTION( insufficient_feeds, "Cannot force settle with no price feed if the asset is not globally settled and the " - "individual settlement pool is not empty" ); + "individual settlement pool (for force-settlements) is not empty" ); } } @@ -1191,7 +1191,33 @@ void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::op bitasset_ptr = &bitasset; return void_result(); -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE + +static optional pay_collateral_fees( database& d, + const asset& pays, + const asset& settled_amount, + const asset_object& asset_to_settle, + const asset_bitasset_data_object& bitasset ) +{ + const auto& head_time = d.head_block_time(); + bool after_core_hardfork_2591 = HARDFORK_CORE_2591_PASSED( head_time ); // Tighter peg (fill settlement at MCOP) + if( after_core_hardfork_2591 && !bitasset.is_prediction_market + && !bitasset.current_feed.settlement_price.is_null() ) + { + price fill_price = bitasset.get_margin_call_order_price(); + try + { + asset settled_amount_by_mcop = pays.multiply_and_round_up( fill_price ); // Throws fc::exception if overflow + if( settled_amount_by_mcop < settled_amount ) + { + asset collateral_fees = settled_amount - settled_amount_by_mcop; + asset_to_settle.accumulate_fee( d, collateral_fees ); + return collateral_fees; + } + } FC_CAPTURE_AND_LOG( (pays)(settled_amount)(fill_price) ) // Catch and log the exception // GCOVR_EXCL_LINE + } + return optional(); +} static extendable_operation_result pay_settle_from_gs_fund( database& d, const asset_settle_evaluator::operation_type& op, @@ -1229,12 +1255,19 @@ static extendable_operation_result pay_settle_from_gs_fund( database& d, d.adjust_balance( op.account, -pays ); asset issuer_fees( 0, bitasset.options.short_backing_asset ); + optional collateral_fees; + if( settled_amount.amount > 0 ) { d.modify( bitasset, [&settled_amount]( asset_bitasset_data_object& obj ){ obj.settlement_fund -= settled_amount.amount; }); + // Calculate and pay collateral fees after HF core-2591 + collateral_fees = pay_collateral_fees( d, pays, settled_amount, asset_to_settle, bitasset ); + if( collateral_fees.valid() ) + settled_amount -= *collateral_fees; + // The account who settles pays market fees to the issuer of the collateral asset after HF core-1780 // // TODO Check whether the HF check can be removed after the HF. @@ -1259,7 +1292,8 @@ static extendable_operation_result pay_settle_from_gs_fund( database& d, result.value.paid = vector({ pays }); result.value.received = vector({ settled_amount }); - result.value.fees = vector({ issuer_fees }); + result.value.fees = collateral_fees.valid() ? vector({ *collateral_fees, issuer_fees }) + : vector({ issuer_fees }); return result; } @@ -1288,6 +1322,12 @@ static extendable_operation_result pay_settle_from_individual_pool( database& d, d.modify( asset_to_settle.dynamic_asset_data_id(d), [&pays]( asset_dynamic_data_object& obj ){ obj.current_supply -= pays.amount; }); + + // Calculate and pay collateral fees after HF core-2591 + optional collateral_fees = pay_collateral_fees( d, pays, settled_amount, asset_to_settle, bitasset ); + if( collateral_fees.valid() ) + settled_amount -= *collateral_fees; + auto issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount, false ); settled_amount -= issuer_fees; @@ -1310,7 +1350,8 @@ static extendable_operation_result pay_settle_from_individual_pool( database& d, result.value.paid = vector({ pays }); result.value.received = vector({ settled_amount }); - result.value.fees = vector({ issuer_fees }); + result.value.fees = collateral_fees.valid() ? vector({ *collateral_fees, issuer_fees }) + : vector({ issuer_fees }); return result; } @@ -1322,18 +1363,18 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: const auto& bitasset = *bitasset_ptr; // Process global settlement fund - if( bitasset.has_settlement() ) + if( bitasset.is_globally_settled() ) return pay_settle_from_gs_fund( d, op, fee_paying_account, *asset_to_settle, bitasset ); // Process individual settlement pool extendable_operation_result result; asset to_settle = op.amount; - if( bitasset.has_individual_settlement() ) + if( bitasset.is_individually_settled_to_fund() ) { result = pay_settle_from_individual_pool( d, op, fee_paying_account, *asset_to_settle, bitasset ); // If the amount to settle is too small, or force settlement is disabled, we return - if( bitasset.has_individual_settlement() || !asset_to_settle->can_force_settle() ) + if( bitasset.is_individually_settled_to_fund() || !asset_to_settle->can_force_settle() ) return result; to_settle -= result.value.paid->front(); @@ -1341,13 +1382,16 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: // Process the rest const auto& head_time = d.head_block_time(); - const auto& maint_time = d.get_dynamic_global_properties().next_maintenance_time; - d.adjust_balance( op.account, -to_settle ); bool after_core_hardfork_2582 = HARDFORK_CORE_2582_PASSED( head_time ); // Price feed issues if( after_core_hardfork_2582 && 0 == to_settle.amount ) return result; + bool after_core_hardfork_2587 = HARDFORK_CORE_2587_PASSED( head_time ); + if( after_core_hardfork_2587 && bitasset.current_feed.settlement_price.is_null() ) + return result; + + d.adjust_balance( op.account, -to_settle ); const auto& settle = d.create( [&op,&to_settle,&head_time,&bitasset](force_settlement_object& s) { s.owner = op.account; @@ -1357,6 +1401,7 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: result.value.new_objects = flat_set({ settle.id }); + const auto& maint_time = d.get_dynamic_global_properties().next_maintenance_time; if( HARDFORK_CORE_2481_PASSED( maint_time ) ) { d.apply_force_settlement( settle, bitasset, *asset_to_settle ); @@ -1364,7 +1409,7 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: return result; -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_operation& o) { try { @@ -1381,7 +1426,7 @@ void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_ const asset_bitasset_data_object& bitasset = base.bitasset_data(d); if( bitasset.is_prediction_market || now <= HARDFORK_CORE_216_TIME ) { - FC_ASSERT( !bitasset.has_settlement(), "No further feeds may be published after a settlement event" ); + FC_ASSERT( !bitasset.is_globally_settled(), "No further feeds may be published after a settlement event" ); } // the settlement price must be quoted in terms of the backing asset @@ -1426,7 +1471,7 @@ void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_ bitasset_ptr = &bitasset; return void_result(); -} FC_CAPTURE_AND_RETHROW((o)) } +} FC_CAPTURE_AND_RETHROW((o)) } // GCOVR_EXCL_LINE void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_operation& o) { try { @@ -1455,7 +1500,7 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope return void_result(); // Feed changed, check whether need to revive the asset and proceed if need - if( bad.has_settlement() // has globally settled, implies head_block_time > HARDFORK_CORE_216_TIME + if( bad.is_globally_settled() // has globally settled, implies head_block_time > HARDFORK_CORE_216_TIME && !bad.current_feed.settlement_price.is_null() ) // has a valid feed { bool should_revive = false; @@ -1489,7 +1534,7 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope d.check_call_orders( base, true, false, bitasset_ptr ); return void_result(); -} FC_CAPTURE_AND_RETHROW((o)) } +} FC_CAPTURE_AND_RETHROW((o)) } // GCOVR_EXCL_LINE /*** @@ -1534,7 +1579,7 @@ void_result asset_claim_fees_evaluator::do_evaluate( const asset_claim_fees_oper } return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE /*** @@ -1557,7 +1602,7 @@ void_result asset_claim_fees_evaluator::do_apply( const asset_claim_fees_operati d.adjust_balance( o.issuer, o.amount_to_claim ); return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE void_result asset_claim_pool_evaluator::do_evaluate( const asset_claim_pool_operation& o ) @@ -1565,7 +1610,7 @@ void_result asset_claim_pool_evaluator::do_evaluate( const asset_claim_pool_oper FC_ASSERT( o.asset_id(db()).issuer == o.issuer, "Asset fee pool may only be claimed by the issuer" ); return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE void_result asset_claim_pool_evaluator::do_apply( const asset_claim_pool_operation& o ) { try { @@ -1582,7 +1627,7 @@ void_result asset_claim_pool_evaluator::do_apply( const asset_claim_pool_operati d.adjust_balance( o.issuer, o.amount_to_claim ); return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE } } // graphene::chain diff --git a/libraries/chain/credit_offer_evaluator.cpp b/libraries/chain/credit_offer_evaluator.cpp index d9413f547b..b24de46ce6 100644 --- a/libraries/chain/credit_offer_evaluator.cpp +++ b/libraries/chain/credit_offer_evaluator.cpp @@ -66,7 +66,7 @@ void_result credit_offer_create_evaluator::do_evaluate(const credit_offer_create "The account is unauthorized by the asset" ); return void_result(); -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE object_id_type credit_offer_create_evaluator::do_apply(const credit_offer_create_operation& op) const { try { @@ -88,7 +88,7 @@ object_id_type credit_offer_create_evaluator::do_apply(const credit_offer_create obj.acceptable_borrowers = op.acceptable_borrowers; }); return new_credit_offer_object.id; -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE void_result credit_offer_delete_evaluator::do_evaluate(const credit_offer_delete_operation& op) { try { @@ -104,7 +104,7 @@ void_result credit_offer_delete_evaluator::do_evaluate(const credit_offer_delete // Note: no asset authorization check here, allow funds to be moved to account balance return void_result(); -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE asset credit_offer_delete_evaluator::do_apply(const credit_offer_delete_operation& op) const { try { @@ -120,7 +120,7 @@ asset credit_offer_delete_evaluator::do_apply(const credit_offer_delete_operatio d.remove( *_offer ); return released; -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE void_result credit_offer_update_evaluator::do_evaluate(const credit_offer_update_operation& op) { try { @@ -180,7 +180,7 @@ void_result credit_offer_update_evaluator::do_evaluate(const credit_offer_update } return void_result(); -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE void_result credit_offer_update_evaluator::do_apply( const credit_offer_update_operation& op) const { try { @@ -224,11 +224,18 @@ void_result credit_offer_update_evaluator::do_apply( const credit_offer_update_o } return void_result(); -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE void_result credit_offer_accept_evaluator::do_evaluate(const credit_offer_accept_operation& op) { try { const database& d = db(); + const auto block_time = d.head_block_time(); + + if( !HARDFORK_CORE_2595_PASSED(block_time) ) + { + FC_ASSERT( !op.extensions.value.auto_repay.valid(), + "auto_repay unavailable until the core-2595 hardfork"); + } _offer = &op.offer_id(d); @@ -297,7 +304,7 @@ void_result credit_offer_accept_evaluator::do_evaluate(const credit_offer_accept } return void_result(); -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE extendable_operation_result credit_offer_accept_evaluator::do_apply( const credit_offer_accept_operation& op) const { try { @@ -325,6 +332,7 @@ extendable_operation_result credit_offer_accept_evaluator::do_apply( const credi obj.collateral_amount = op.collateral.amount; obj.fee_rate = _offer->fee_rate; obj.latest_repay_time = repay_time; + obj.auto_repay = ( op.extensions.value.auto_repay.valid() ? *op.extensions.value.auto_repay : 0 ); }); if( _deal_summary != nullptr ) @@ -360,7 +368,7 @@ extendable_operation_result credit_offer_accept_evaluator::do_apply( const credi result.value.impacted_accounts = flat_set({ _offer->owner_account }); return result; -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE void_result credit_deal_repay_evaluator::do_evaluate(const credit_deal_repay_operation& op) { try { @@ -377,7 +385,7 @@ void_result credit_deal_repay_evaluator::do_evaluate(const credit_deal_repay_ope // Note: the result can be larger than 64 bit, but since we don't store it, it is allowed auto required_fee = ( ( ( fc::uint128_t( op.repay_amount.amount.value ) * _deal->fee_rate ) - + GRAPHENE_FEE_RATE_DENOM ) - 1 ) / GRAPHENE_FEE_RATE_DENOM; // Round up + + GRAPHENE_FEE_RATE_DENOM ) - 1 ) / GRAPHENE_FEE_RATE_DENOM; // Round up FC_ASSERT( fc::uint128_t(op.credit_fee.amount.value) >= required_fee, "Insuffient credit fee, requires ${r}, offered ${p}", @@ -392,7 +400,7 @@ void_result credit_deal_repay_evaluator::do_evaluate(const credit_deal_repay_ope "The owner of the credit offer is unauthorized by the repaying asset" ); return void_result(); -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE extendable_operation_result credit_deal_repay_evaluator::do_apply( const credit_deal_repay_operation& op) const { try { @@ -442,6 +450,9 @@ extendable_operation_result credit_deal_repay_evaluator::do_apply( const credit_ } else // to partially repay { + // Note: + // Due to rounding, it is possible that the account is paying too much debt asset for too little collateral, + // in extreme cases, the amount to release can be zero. auto amount_to_release = ( fc::uint128_t( op.repay_amount.amount.value ) * _deal->collateral_amount.value ) / _deal->debt_amount.value; // Round down FC_ASSERT( amount_to_release < fc::uint128_t( _deal->collateral_amount.value ), "Internal error" ); @@ -459,6 +470,33 @@ extendable_operation_result credit_deal_repay_evaluator::do_apply( const credit_ result.value.received = vector({ collateral_released }); return result; -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE + +void_result credit_deal_update_evaluator::do_evaluate(const credit_deal_update_operation& op) +{ try { + const database& d = db(); + const auto block_time = d.head_block_time(); + + FC_ASSERT( HARDFORK_CORE_2595_PASSED(block_time), "Not allowed until the core-2595 hardfork" ); + + _deal = &op.deal_id(d); + + FC_ASSERT( _deal->borrower == op.account, "A credit deal can only be updated by the borrower" ); + + FC_ASSERT( _deal->auto_repay != op.auto_repay, "The automatic repayment type does not change" ); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE + +void_result credit_deal_update_evaluator::do_apply( const credit_deal_update_operation& op) const +{ try { + database& d = db(); + + d.modify( *_deal, [&op]( credit_deal_object& obj ){ + obj.auto_repay = op.auto_repay; + }); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE } } // graphene::chain diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 92440e4dbe..e5da510301 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -63,7 +63,7 @@ bool database::is_known_transaction( const transaction_id_type& id )const block_id_type database::get_block_id_for_num( uint32_t block_num )const { try { return _block_id_to_block.fetch_block_id( block_num ); -} FC_CAPTURE_AND_RETHROW( (block_num) ) } +} FC_CAPTURE_AND_RETHROW( (block_num) ) } // GCOVR_EXCL_LINE optional database::fetch_block_by_id( const block_id_type& id )const { @@ -92,7 +92,8 @@ const signed_transaction& database::get_recent_transaction(const transaction_id_ std::vector database::get_block_ids_on_fork(block_id_type head_of_fork) const { - pair branches = _fork_db.fetch_branch_from(head_block_id(), head_of_fork); + pair branches + = _fork_db.fetch_branch_from(head_block_id(), head_of_fork); if( !((branches.first.back()->previous_id() == branches.second.back()->previous_id())) ) { edump( (head_of_fork) @@ -180,7 +181,8 @@ bool database::_push_block(const signed_block& new_block) // remove the rest of branches.first from the fork_db, those blocks are invalid while( ritr != branches.first.rend() ) { - ilog( "removing block from fork_db #${n} ${id}", ("n",(*ritr)->data.block_num())("id",(*ritr)->id) ); + ilog( "removing block from fork_db #${n} ${id}", + ("n",(*ritr)->data.block_num())("id",(*ritr)->id) ); _fork_db.remove( (*ritr)->id ); ++ritr; } @@ -225,7 +227,7 @@ bool database::_push_block(const signed_block& new_block) } return false; -} FC_CAPTURE_AND_RETHROW( (new_block) ) } +} FC_CAPTURE_AND_RETHROW( (new_block) ) } // GCOVR_EXCL_LINE void database::verify_signing_witness( const signed_block& new_block, const fork_item& fork_entry )const { @@ -275,7 +277,7 @@ processed_transaction database::push_transaction( const precomputable_transactio result = _push_transaction( trx ); } ); return result; -} FC_CAPTURE_AND_RETHROW( (trx) ) } +} FC_CAPTURE_AND_RETHROW( (trx) ) } // GCOVR_EXCL_LINE processed_transaction database::_push_transaction( const precomputable_transaction& trx ) { @@ -308,18 +310,23 @@ processed_transaction database::validate_transaction( const signed_transaction& return _apply_transaction( trx ); } -class push_proposal_nesting_guard { +class undo_session_nesting_guard { public: - push_proposal_nesting_guard( uint32_t& nesting_counter, const database& db ) + undo_session_nesting_guard( uint32_t& nesting_counter, const database& db ) : orig_value(nesting_counter), counter(nesting_counter) { - FC_ASSERT( counter < db.get_global_properties().active_witnesses.size() * 2, "Max proposal nesting depth exceeded!" ); - counter++; + FC_ASSERT( counter < db.get_global_properties().active_witnesses.size() * 2, + "Max undo session nesting depth exceeded!" ); + ++counter; } - ~push_proposal_nesting_guard() + ~undo_session_nesting_guard() { - if( --counter != orig_value ) - elog( "Unexpected proposal nesting count value: ${n} != ${o}", ("n",counter)("o",orig_value) ); + --counter; + // GCOVR_EXCL_START + // Defensive code, should not happen + if( counter != orig_value ) + elog( "Unexpected undo session nesting count value: ${n} != ${o}", ("n",counter)("o",orig_value) ); + // GCOVR_EXCL_STOP } private: const uint32_t orig_value; @@ -335,9 +342,10 @@ processed_transaction database::push_proposal(const proposal_object& proposal) processed_transaction ptrx(proposal.proposed_transaction); eval_state._trx = &ptrx; size_t old_applied_ops_size = _applied_ops.size(); + auto old_vop = _current_virtual_op; try { - push_proposal_nesting_guard guard( _push_proposal_nesting_depth, *this ); + undo_session_nesting_guard guard( _undo_session_nesting_depth, *this ); if( _undo_db.size() >= _undo_db.max_size() ) _undo_db.set_max_size( _undo_db.size() + 1 ); auto session = _undo_db.start_undo_session(true); @@ -360,6 +368,7 @@ processed_transaction database::push_proposal(const proposal_object& proposal) } else { + _current_virtual_op = old_vop; _applied_ops.resize( old_applied_ops_size ); } wlog( "${e}", ("e",e.to_detail_string() ) ); @@ -368,7 +377,7 @@ processed_transaction database::push_proposal(const proposal_object& proposal) ptrx.operation_results = std::move(eval_state.operation_results); return ptrx; -} FC_CAPTURE_AND_RETHROW( (proposal) ) } +} FC_CAPTURE_AND_RETHROW( (proposal) ) } // GCOVR_EXCL_LINE signed_block database::generate_block( fc::time_point_sec when, @@ -383,7 +392,7 @@ signed_block database::generate_block( result = _generate_block( when, witness_id, block_signing_private_key ); } ); return result; -} FC_CAPTURE_AND_RETHROW() } +} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE signed_block database::_generate_block( fc::time_point_sec when, @@ -424,9 +433,10 @@ signed_block database::_generate_block( FC_ASSERT( witness_id(*this).signing_key == block_signing_private_key.get_public_key() ); } - static const size_t max_partial_block_header_size = fc::raw::pack_size( signed_block_header() ) - - fc::raw::pack_size( witness_id_type() ) // witness_id - + 3; // max space to store size of transactions (out of block header), + static const size_t max_partial_block_header_size = ( fc::raw::pack_size( signed_block_header() ) + - fc::raw::pack_size( witness_id_type() ) ) // witness_id + + 3; // max space to store size of transactions + // (out of block header), // +3 means 3*7=21 bits so it's practically safe const size_t max_block_header_size = max_partial_block_header_size + fc::raw::pack_size( witness_id ); auto maximum_block_size = get_global_properties().parameters.maximum_block_size; @@ -500,10 +510,11 @@ signed_block database::_generate_block( if( 0 == (skip & skip_witness_signature) ) pending_block.sign( block_signing_private_key ); - push_block( pending_block, skip | skip_transaction_signatures ); // skip authority check when pushing self-generated blocks + push_block( pending_block, skip | skip_transaction_signatures ); // skip authority check when pushing + // self-generated blocks return pending_block; -} FC_CAPTURE_AND_RETHROW( (witness_id) ) } +} FC_CAPTURE_AND_RETHROW( (witness_id) ) } // GCOVR_EXCL_LINE /** * Removes the most recent block from the database and @@ -522,15 +533,17 @@ void database::pop_block() FC_ASSERT( fork_db_head, "Trying to pop() block that's not in fork database!?" ); } pop_undo(); - _popped_tx.insert( _popped_tx.begin(), fork_db_head->data.transactions.begin(), fork_db_head->data.transactions.end() ); -} FC_CAPTURE_AND_RETHROW() } + _popped_tx.insert( _popped_tx.begin(), + fork_db_head->data.transactions.begin(), + fork_db_head->data.transactions.end() ); +} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE void database::clear_pending() { try { assert( (_pending_tx.size() == 0) || _pending_tx_session.valid() ); _pending_tx.clear(); _pending_tx_session.reset(); -} FC_CAPTURE_AND_RETHROW() } +} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE uint32_t database::push_applied_operation( const operation& op, bool is_virtual /* = true */ ) { @@ -665,7 +678,7 @@ void database::_apply_block( const signed_block& next_block ) _applied_ops.clear(); notify_changed_objects(); -} FC_CAPTURE_AND_RETHROW( (next_block.block_num()) ) } +} FC_CAPTURE_AND_RETHROW( (next_block.block_num()) ) } // GCOVR_EXCL_LINE /** * @note if a @c processed_transaction is passed in, it is cast into @c signed_transaction here. @@ -766,7 +779,7 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx "Unpaid SameT Fund debt detected" ); return ptrx; -} FC_CAPTURE_AND_RETHROW( (trx) ) } +} FC_CAPTURE_AND_RETHROW( (trx) ) } // GCOVR_EXCL_LINE operation_result database::apply_operation( transaction_evaluation_state& eval_state, const operation& op, bool is_virtual /* = true */ ) @@ -781,7 +794,35 @@ operation_result database::apply_operation( transaction_evaluation_state& eval_s auto result = eval->evaluate( eval_state, op, true ); set_applied_operation_result( op_id, result ); return result; -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE + +operation_result database::try_push_virtual_operation( transaction_evaluation_state& eval_state, const operation& op ) +{ + operation_validate( op ); + + // Note: these variables could be updated during the apply_operation() call + size_t old_applied_ops_size = _applied_ops.size(); + auto old_vop = _current_virtual_op; + + try + { + undo_session_nesting_guard guard( _undo_session_nesting_depth, *this ); + if( _undo_db.size() >= _undo_db.max_size() ) + _undo_db.set_max_size( _undo_db.size() + 1 ); + auto temp_session = _undo_db.start_undo_session(true); + auto result = apply_operation( eval_state, op ); // This is a virtual operation + temp_session.merge(); + return result; + } + catch( const fc::exception& e ) + { + wlog( "Failed to push virtual operation ${op} at block ${n}; exception was ${e}", + ("op", op)("n", head_block_num())("e", e.to_detail_string()) ); + _current_virtual_op = old_vop; + _applied_ops.resize( old_applied_ops_size ); + throw; + } +} const witness_object& database::validate_block_header( uint32_t skip, const signed_block& next_block )const { @@ -859,7 +900,8 @@ fc::future database::precompute_parallel( const signed_block& block, const for( size_t base = 0; base < block.transactions.size(); base += chunk_size ) workers.push_back( fc::do_parallel( [this,&block,base,chunk_size,skip] () { _precompute_parallel( &block.transactions[base], - base + chunk_size < block.transactions.size() ? chunk_size : block.transactions.size() - base, + ( ( base + chunk_size ) < block.transactions.size() ) ? chunk_size + : ( block.transactions.size() - base ), skip ); }) ); } diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 527902aa52..b49439cf76 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -97,6 +97,7 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); @@ -133,6 +134,7 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); @@ -146,6 +148,7 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); } void database::initialize_indexes() diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 91d4edc88d..a273d73fa4 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -305,7 +305,7 @@ void database::update_active_witnesses() }); }); -} FC_CAPTURE_AND_RETHROW() } +} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE void database::update_active_committee_members() { try { @@ -412,7 +412,7 @@ void database::update_active_committee_members() std::inserter(gp.active_committee_members, gp.active_committee_members.begin()), [](const committee_member_object& d) { return d.get_id(); }); }); -} FC_CAPTURE_AND_RETHROW() } +} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE void database::initialize_budget_record( fc::time_point_sec now, budget_record& rec )const { @@ -564,7 +564,7 @@ void database::process_budget() // available_funds is money we could spend, but don't want to. // we simply let it evaporate back into the reserve. } - FC_CAPTURE_AND_RETHROW() + FC_CAPTURE_AND_RETHROW() // GCOVR_EXCL_LINE } template< typename Visitor > @@ -984,7 +984,7 @@ void database::process_bitassets() for( const auto& d : get_index_type().indices() ) { modify( d, update_bitasset ); - if( d.has_settlement() ) + if( d.is_globally_settled() ) process_bids(d); } } diff --git a/libraries/chain/db_management.cpp b/libraries/chain/db_management.cpp index d78051ecc1..98e754fd51 100644 --- a/libraries/chain/db_management.cpp +++ b/libraries/chain/db_management.cpp @@ -65,7 +65,7 @@ void database::reindex( fc::path data_dir ) ilog( "reindexing blockchain" ); auto start = fc::time_point::now(); const auto last_block_num = last_block->block_num(); - uint32_t undo_point = last_block_num < GRAPHENE_MAX_UNDO_HISTORY ? 0 : last_block_num - GRAPHENE_MAX_UNDO_HISTORY; + uint32_t undo_point = last_block_num < GRAPHENE_MAX_UNDO_HISTORY ? 0 : (last_block_num - GRAPHENE_MAX_UNDO_HISTORY); ilog( "Replaying blocks, starting at ${next}...", ("next",head_block_num() + 1) ); if( head_block_num() >= undo_point ) @@ -88,10 +88,11 @@ void database::reindex( fc::path data_dir ) if( next_block_num <= last_block_num && blocks.size() < 20 ) { const size_t processed_block_size = _block_id_to_block.blocks_current_position(); - fc::optional< signed_block > block = _block_id_to_block.fetch_by_number( next_block_num++ ); + fc::optional< signed_block > block = _block_id_to_block.fetch_by_number( next_block_num ); + ++next_block_num; if( block.valid() ) { - if( block->timestamp >= last_block->timestamp - gpo.parameters.maximum_time_until_expiration ) + if( block->timestamp >= (last_block->timestamp - gpo.parameters.maximum_time_until_expiration) ) skip &= (uint32_t)(~skip_transaction_dupe_check); blocks.emplace( processed_block_size, std::move(*block), fc::future() ); std::get<2>(blocks.back()) = precompute_parallel( std::get<1>(blocks.back()), skip ); @@ -104,13 +105,12 @@ void database::reindex( fc::path data_dir ) { fc::optional< block_id_type > last_id = _block_id_to_block.last_id(); // this can trigger if we attempt to e.g. read a file that has block #2 but no block #1 - if( !last_id.valid() ) - break; + // OR // we've caught up to the gap - if( block_header::num_from_id( *last_id ) <= i ) + if( !last_id.valid() || block_header::num_from_id( *last_id ) <= i ) break; _block_id_to_block.remove( *last_id ); - dropped_count++; + ++dropped_count; } wlog( "Dropped ${n} blocks from after the gap", ("n", dropped_count) ); next_block_num = last_block_num + 1; // don't load more blocks @@ -128,8 +128,8 @@ void database::reindex( fc::path data_dir ) size_t current_pos = std::get<0>(blocks.front()); if( current_pos > total_block_size ) total_block_size = current_pos; - bysize << std::fixed << std::setprecision(5) << double(current_pos) / total_block_size * 100; - bynum << std::fixed << std::setprecision(5) << double(i)*100/last_block_num; + bysize << std::fixed << std::setprecision(5) << (100 * double(current_pos) / total_block_size); + bynum << std::fixed << std::setprecision(5) << (100 * double(i) / last_block_num); ilog( " [by size: ${size}% ${processed} of ${total}] [by num: ${num}% ${i} of ${last}]", ("size", bysize.str()) @@ -154,7 +154,7 @@ void database::reindex( fc::path data_dir ) push_block( block, skip ); } blocks.pop(); - i++; + ++i; } } _undo_db.enable(); @@ -227,17 +227,16 @@ void database::open( FC_CAPTURE_LOG_AND_RETHROW( (data_dir) ) } -void database::close(bool rewind) +void database::close(bool rewinding) { if (!_opened) return; - // TODO: Save pending tx's on close() clear_pending(); // pop all of the blocks that we can given our undo history, this should // throw when there is no more undo history to pop - if( rewind ) + if( rewinding ) { try { diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 05c78c0b54..01043fcb85 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -53,7 +53,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s if( !mia.is_market_issued() ) return false; const asset_bitasset_data_object& bitasset = bitasset_ptr ? *bitasset_ptr : mia.bitasset_data(*this); - if( bitasset.has_settlement() ) return true; // already force settled + if( bitasset.is_globally_settled() ) return true; // already globally settled auto settle_price = bitasset.current_feed.settlement_price; if( settle_price.is_null() ) return false; // no feed @@ -246,7 +246,10 @@ void database::globally_settle_asset_impl( const asset_object& mia, bool check_margin_calls ) { try { const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this); - FC_ASSERT( !bitasset.has_settlement(), "black swan already occurred, it should not happen again" ); + // GCOVR_EXCL_START + // Defensive code, normally it should not fail + FC_ASSERT( !bitasset.is_globally_settled(), "black swan already occurred, it should not happen again" ); + // GCOVR_EXCL_STOP asset collateral_gathered( 0, bitasset.options.short_backing_asset ); @@ -315,13 +318,10 @@ void database::globally_settle_asset_impl( const asset_object& mia, "Internal error: unable to close margin call ${o}", ("o", order) ); } - // Move the individual settlement order to the GS fund + // Remove the individual settlement order const limit_order_object* limit_ptr = find_settled_debt_order( bitasset.asset_id ); if( limit_ptr ) - { - collateral_gathered.amount += limit_ptr->for_sale; remove( *limit_ptr ); - } // Move individual settlement fund to the GS fund collateral_gathered.amount += bitasset.individual_settlement_fund; @@ -335,7 +335,7 @@ void database::globally_settle_asset_impl( const asset_object& mia, obj.settlement_fund = collateral_gathered.amount; }); -} FC_CAPTURE_AND_RETHROW( (mia)(settlement_price) ) } +} FC_CAPTURE_AND_RETHROW( (mia)(settlement_price) ) } // GCOVR_EXCL_LINE void database::individually_settle( const asset_bitasset_data_object& bitasset, const call_order_object& order ) { @@ -355,22 +355,44 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, auto margin_call_fee = order_collateral - fund_receives; - if( bsrm_type::individual_settlement_to_fund == bsrm ) // settle to fund - { - modify( bitasset, [&order,&fund_receives]( asset_bitasset_data_object& obj ){ - obj.individual_settlement_debt += order.debt; - obj.individual_settlement_fund += fund_receives.amount; - }); - } - else // settle to order + modify( bitasset, [&order,&fund_receives]( asset_bitasset_data_object& obj ){ + obj.individual_settlement_debt += order.debt; + obj.individual_settlement_fund += fund_receives.amount; + }); + + if( bsrm_type::individual_settlement_to_order == bsrm ) // settle to order { + const auto& head_time = head_block_time(); + bool after_core_hardfork_2591 = HARDFORK_CORE_2591_PASSED( head_time ); // Tighter peg (fill debt order at MCOP) + const limit_order_object* limit_ptr = find_settled_debt_order( bitasset.asset_id ); if( limit_ptr ) { - modify( *limit_ptr, [&order,&fund_receives]( limit_order_object& obj ) { - obj.for_sale += fund_receives.amount; - obj.sell_price.base.amount = obj.for_sale; - obj.sell_price.quote.amount += order.debt; + modify( *limit_ptr, [after_core_hardfork_2591,&bitasset]( limit_order_object& obj ) { + // TODO fix duplicate code + bool sell_all = true; + if( after_core_hardfork_2591 ) + { + obj.sell_price = ~bitasset.get_margin_call_order_price(); + asset settled_debt( bitasset.individual_settlement_debt, obj.receive_asset_id() ); + try + { + obj.for_sale = settled_debt.multiply_and_round_up( obj.sell_price ).amount; // may overflow + // Note: the "=" below is for the consistency of order matching logic + if( obj.for_sale <= bitasset.individual_settlement_fund ) + sell_all = false; + } + catch( fc::exception& e ) // catch the overflow + { + // do nothing + dlog( e.to_detail_string() ); + } + } + if( sell_all ) + { + obj.for_sale = bitasset.individual_settlement_fund; + obj.sell_price = ~bitasset.get_individual_settlement_price(); + } } ); } else @@ -399,11 +421,14 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, void database::revive_bitasset( const asset_object& bitasset, const asset_bitasset_data_object& bad ) { try { + // GCOVR_EXCL_START + // Defensive code, normally none of these should fail FC_ASSERT( bitasset.is_market_issued() ); FC_ASSERT( bitasset.id == bad.asset_id ); - FC_ASSERT( bad.has_settlement() ); + FC_ASSERT( bad.is_globally_settled() ); FC_ASSERT( !bad.is_prediction_market ); FC_ASSERT( !bad.current_feed.settlement_price.is_null() ); + // GCOVR_EXCL_STOP const asset_dynamic_data_object& bdd = bitasset.dynamic_asset_data_id(*this); if( bdd.current_supply > 0 ) @@ -420,13 +445,16 @@ void database::revive_bitasset( const asset_object& bitasset, const asset_bitass FC_ASSERT( bad.settlement_fund == 0 ); _cancel_bids_and_revive_mpa( bitasset, bad ); -} FC_CAPTURE_AND_RETHROW( (bitasset) ) } +} FC_CAPTURE_AND_RETHROW( (bitasset) ) } // GCOVR_EXCL_LINE void database::_cancel_bids_and_revive_mpa( const asset_object& bitasset, const asset_bitasset_data_object& bad ) { try { + // GCOVR_EXCL_START + // Defensive code, normally none of these should fail FC_ASSERT( bitasset.is_market_issued() ); - FC_ASSERT( bad.has_settlement() ); + FC_ASSERT( bad.is_globally_settled() ); FC_ASSERT( !bad.is_prediction_market ); + // GCOVR_EXCL_STOP // cancel remaining bids const auto& bid_idx = get_index_type< collateral_bid_index >().indices().get(); @@ -444,7 +472,7 @@ void database::_cancel_bids_and_revive_mpa( const asset_object& bitasset, const obj.settlement_price = price(); obj.settlement_fund = 0; }); -} FC_CAPTURE_AND_RETHROW( (bitasset) ) } +} FC_CAPTURE_AND_RETHROW( (bitasset) ) } // GCOVR_EXCL_LINE void database::cancel_bid(const collateral_bid_object& bid, bool create_virtual_op) { @@ -509,7 +537,7 @@ void database::cancel_limit_order( const limit_order_object& order, bool create_ // 1. due to expiration: always deduct a fee if there is any fee deferred // 2. due to cull_small: deduct a fee after hard fork 604, but not before (will set skip_cancel_fee) const account_statistics_object* seller_acc_stats = nullptr; - const asset_dynamic_data_object* fee_asset_dyn_data = nullptr; + const asset_dynamic_data_object* deferred_fee_asset_dyn_data = nullptr; limit_order_cancel_operation vop; share_type deferred_fee = order.deferred_fee; asset deferred_paid_fee = order.deferred_paid_fee; @@ -548,8 +576,8 @@ void database::cancel_limit_order( const limit_order_object& order, bool create_ fee128 /= order.deferred_fee.value; share_type cancel_fee_amount = static_cast(fee128); // cancel_fee should be positive, pay it to asset's accumulated_fees - fee_asset_dyn_data = &deferred_paid_fee.asset_id(*this).dynamic_asset_data_id(*this); - modify( *fee_asset_dyn_data, [&cancel_fee_amount](asset_dynamic_data_object& addo) { + deferred_fee_asset_dyn_data = &deferred_paid_fee.asset_id(*this).dynamic_asset_data_id(*this); + modify( *deferred_fee_asset_dyn_data, [&cancel_fee_amount](asset_dynamic_data_object& addo) { addo.accumulated_fees += cancel_fee_amount; }); // cancel_fee should be no more than deferred_paid_fee @@ -585,15 +613,32 @@ void database::cancel_limit_order( const limit_order_object& order, bool create_ { adjust_balance(order.seller, deferred_paid_fee); // be here, must have: fee_asset != CORE - if( !fee_asset_dyn_data ) - fee_asset_dyn_data = &deferred_paid_fee.asset_id(*this).dynamic_asset_data_id(*this); - modify( *fee_asset_dyn_data, [&](asset_dynamic_data_object& addo) { + if( !deferred_fee_asset_dyn_data ) + deferred_fee_asset_dyn_data = &deferred_paid_fee.asset_id(*this).dynamic_asset_data_id(*this); + modify( *deferred_fee_asset_dyn_data, [&deferred_fee](asset_dynamic_data_object& addo) { addo.fee_pool += deferred_fee; }); } if( create_virtual_op ) - push_applied_operation( vop ); + { + auto op_id = push_applied_operation( vop ); + set_applied_operation_result( op_id, refunded ); + } + + cleanup_and_remove_limit_order( order ); +} + +void database::cleanup_and_remove_limit_order( const limit_order_object& order ) +{ + // Unlink the linked take profit order if it exists + if( order.take_profit_order_id.valid() ) + { + const auto& take_profit_order = (*order.take_profit_order_id)(*this); + modify( take_profit_order, []( limit_order_object& loo ) { + loo.take_profit_order_id.reset(); + }); + } remove(order); } @@ -760,7 +805,7 @@ bool database::apply_order(const limit_order_object& new_order_object) sell_abd = &sell_asset.bitasset_data( *this ); if( sell_abd->options.short_backing_asset == recv_asset_id && !sell_abd->is_prediction_market - && !sell_abd->has_settlement() + && !sell_abd->is_globally_settled() && !sell_abd->current_feed.settlement_price.is_null() ) { if( before_core_hardfork_1270 ) { @@ -901,11 +946,14 @@ void database::apply_force_settlement( const force_settlement_object& new_settle { // Defensive checks auto maint_time = get_dynamic_global_properties().next_maintenance_time; + // GCOVR_EXCL_START + // Defensive code, normally none of these should fail FC_ASSERT( HARDFORK_CORE_2481_PASSED( maint_time ), "Internal error: hard fork core-2481 not passed" ); FC_ASSERT( new_settlement.balance.asset_id == bitasset.asset_id, "Internal error: asset type mismatch" ); FC_ASSERT( !bitasset.is_prediction_market, "Internal error: asset is a prediction market" ); - FC_ASSERT( !bitasset.has_settlement(), "Internal error: asset is globally settled already" ); + FC_ASSERT( !bitasset.is_globally_settled(), "Internal error: asset is globally settled already" ); FC_ASSERT( !bitasset.current_feed.settlement_price.is_null(), "Internal error: no sufficient price feeds" ); + // GCOVR_EXCL_STOP auto head_time = head_block_time(); bool after_core_hardfork_2582 = HARDFORK_CORE_2582_PASSED( head_time ); // Price feed issues @@ -986,28 +1034,31 @@ static database::match_result_type get_match_result( bool taker_filled, bool mak /** * Matches the two orders, the first parameter is taker, the second is maker. * - * @return a bit field indicating which orders were filled (and thus removed) - * - * 0 - no orders were matched - * 1 - taker was filled - * 2 - maker was filled - * 3 - both were filled + * @return which orders were filled (and thus removed) */ database::match_result_type database::match( const limit_order_object& taker, const limit_order_object& maker, const price& match_price ) { + // GCOVR_EXCL_START + // Defensive code, normally none of these should fail FC_ASSERT( taker.sell_price.quote.asset_id == maker.sell_price.base.asset_id ); FC_ASSERT( taker.sell_price.base.asset_id == maker.sell_price.quote.asset_id ); FC_ASSERT( taker.for_sale > 0 && maker.for_sale > 0 ); + // GCOVR_EXCL_STOP return maker.is_settled_debt ? match_limit_settled_debt( taker, maker, match_price ) : match_limit_normal_limit( taker, maker, match_price ); } +/// Match a normal limit order with another normal limit order database::match_result_type database::match_limit_normal_limit( const limit_order_object& taker, const limit_order_object& maker, const price& match_price ) { + // GCOVR_EXCL_START + // Defensive code, normally none of these should fail FC_ASSERT( !maker.is_settled_debt, "Internal error: maker is settled debt" ); + FC_ASSERT( !taker.is_settled_debt, "Internal error: taker is settled debt" ); + // GCOVR_EXCL_STOP auto taker_for_sale = taker.amount_for_sale(); auto maker_for_sale = maker.amount_for_sale(); @@ -1077,23 +1128,30 @@ database::match_result_type database::match_limit_normal_limit( const limit_orde return result; } -// When matching a limit order against settled debt, the maker actually behaviors like a call order +/// When matching a limit order against settled debt, the maker actually behaviors like a call order database::match_result_type database::match_limit_settled_debt( const limit_order_object& taker, const limit_order_object& maker, const price& match_price ) { + // GCOVR_EXCL_START + // Defensive code, normally none of these should fail FC_ASSERT( maker.is_settled_debt, "Internal error: maker is not settled debt" ); + FC_ASSERT( !taker.is_settled_debt, "Internal error: taker is settled debt" ); + // GCOVR_EXCL_STOP bool cull_taker = false; bool maker_filled = false; + const auto& mia = maker.receive_asset_id()(*this); + const auto& bitasset = mia.bitasset_data(*this); + auto usd_for_sale = taker.amount_for_sale(); - auto usd_to_buy = maker.sell_price.quote; + auto usd_to_buy = asset( bitasset.individual_settlement_debt, maker.receive_asset_id() ); asset call_receives; asset order_receives; if( usd_to_buy > usd_for_sale ) { // fill taker limit order - order_receives = usd_for_sale * match_price; // round down here, in favor of call order + order_receives = usd_for_sale * match_price; // round down here, in favor of "call order" // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop. // In this case, we see it as filled and cancel it later @@ -1117,16 +1175,38 @@ database::match_result_type database::match_limit_settled_debt( const limit_orde // seller, pays, receives, ... bool taker_filled = fill_limit_order( taker, call_receives, order_receives, cull_taker, match_price, false ); - // Reduce current supply - const asset_dynamic_data_object& mia_ddo = call_receives.asset_id(*this).dynamic_asset_data_id(*this); - modify( mia_ddo, [&call_receives]( asset_dynamic_data_object& ao ){ + const auto& head_time = head_block_time(); + bool after_core_hardfork_2591 = HARDFORK_CORE_2591_PASSED( head_time ); // Tighter peg (fill debt order at MCOP) + + asset call_pays = order_receives; + if( maker_filled ) // Regardless of hf core-2591 + call_pays.amount = bitasset.individual_settlement_fund; + else if( maker.for_sale != bitasset.individual_settlement_fund ) // implies hf core-2591 + call_pays = call_receives * bitasset.get_individual_settlement_price(); // round down, in favor of "call order" + if( call_pays < order_receives ) // be defensive, maybe unnecessary + { // GCOVR_EXCL_START + wlog( "Unexpected scene: call_pays < order_receives" ); + call_pays = order_receives; + } // GCOVR_EXCL_STOP + asset collateral_fee = call_pays - order_receives; + + // Reduce current supply, and accumulate collateral fees + const asset_dynamic_data_object& mia_ddo = mia.dynamic_asset_data_id(*this); + modify( mia_ddo, [&call_receives,&collateral_fee]( asset_dynamic_data_object& ao ){ ao.current_supply -= call_receives.amount; + ao.accumulated_collateral_fees += collateral_fee.amount; }); // Push fill_order vitual operation // id, seller, pays, receives, ... - push_applied_operation( fill_order_operation( maker.id, maker.seller, order_receives, call_receives, - asset(0, call_receives.asset_id), match_price, true ) ); + push_applied_operation( fill_order_operation( maker.id, maker.seller, call_pays, call_receives, + collateral_fee, match_price, true ) ); + + // Update bitasset data + modify( bitasset, [&call_receives,&call_pays]( asset_bitasset_data_object& obj ){ + obj.individual_settlement_debt -= call_receives.amount; + obj.individual_settlement_fund -= call_pays.amount; + }); // Update the maker order // Note: CORE asset in settled debt is not counted in account_stats.total_core_in_orders @@ -1134,17 +1214,130 @@ database::match_result_type database::match_limit_settled_debt( const limit_orde remove( maker ); else { - modify( maker, [&order_receives,&call_receives]( limit_order_object& obj ) { - obj.for_sale -= order_receives.amount; - obj.sell_price.base.amount = obj.for_sale; - obj.sell_price.quote.amount -= call_receives.amount; + modify( maker, [after_core_hardfork_2591,&bitasset]( limit_order_object& obj ) { + if( after_core_hardfork_2591 ) + { + // Note: for simplicity, only update price when necessary + asset settled_debt( bitasset.individual_settlement_debt, obj.receive_asset_id() ); + obj.for_sale = settled_debt.multiply_and_round_up( obj.sell_price ).amount; + if( obj.for_sale > bitasset.individual_settlement_fund ) // be defensive, maybe unnecessary + { // GCOVR_EXCL_START + wlog( "Unexpected scene: obj.for_sale > bitasset.individual_settlement_fund" ); + obj.for_sale = bitasset.individual_settlement_fund; + obj.sell_price = ~bitasset.get_individual_settlement_price(); + } // GCOVR_EXCL_STOP + } + else + { + obj.for_sale = bitasset.individual_settlement_fund; + obj.sell_price = ~bitasset.get_individual_settlement_price(); + } + // Note: filled_amount is not updated, but it should be fine }); + // Note: + // After the price is updated, it is possible that the order can be matched with another order on the order + // book, which may then be matched with more other orders. For simplicity, we don't do more matching here. } match_result_type result = get_match_result( taker_filled, maker_filled ); return result; } +/// When matching a settled debt order against a limit order, the taker actually behaviors like a call order +// TODO fix duplicate code +database::match_result_type database::match_settled_debt_limit( const limit_order_object& taker, + const limit_order_object& maker, const price& match_price ) +{ + // GCOVR_EXCL_START + // Defensive code, normally none of these should fail + FC_ASSERT( !maker.is_settled_debt, "Internal error: maker is settled debt" ); + FC_ASSERT( taker.is_settled_debt, "Internal error: taker is not settled debt" ); + // GCOVR_EXCL_STOP + + bool taker_filled = false; + + const auto& mia = taker.receive_asset_id()(*this); + const auto& bitasset = mia.bitasset_data(*this); + + auto usd_for_sale = maker.amount_for_sale(); + auto usd_to_buy = asset( bitasset.individual_settlement_debt, taker.receive_asset_id() ); + + asset call_receives; + asset order_receives; + if( usd_to_buy > usd_for_sale ) + { // fill maker limit order + order_receives = usd_for_sale * match_price; // round down here, in favor of call order + + // Be here, the limit order won't be paying something for nothing, since if it would, it would have + // been cancelled elsewhere already (a maker limit order won't be paying something for nothing). + + call_receives = order_receives.multiply_and_round_up( match_price ); + } + else + { // fill taker "call order" + call_receives = usd_to_buy; + order_receives = call_receives.multiply_and_round_up( match_price ); // round up here, in favor of limit order + taker_filled = true; + } + + asset call_pays = order_receives; + if( taker_filled ) + call_pays.amount = bitasset.individual_settlement_fund; + else if( taker.for_sale != bitasset.individual_settlement_fund ) + call_pays = call_receives * bitasset.get_individual_settlement_price(); // round down, in favor of "call order" + if( call_pays < order_receives ) // be defensive, maybe unnecessary + { // GCOVR_EXCL_START + wlog( "Unexpected scene: call_pays < order_receives" ); + call_pays = order_receives; + } // GCOVR_EXCL_STOP + asset collateral_fee = call_pays - order_receives; + + // Reduce current supply, and accumulate collateral fees + const asset_dynamic_data_object& mia_ddo = mia.dynamic_asset_data_id(*this); + modify( mia_ddo, [&call_receives,&collateral_fee]( asset_dynamic_data_object& ao ){ + ao.current_supply -= call_receives.amount; + ao.accumulated_collateral_fees += collateral_fee.amount; + }); + + // Push fill_order vitual operation + // id, seller, pays, receives, ... + push_applied_operation( fill_order_operation( taker.id, taker.seller, call_pays, call_receives, + collateral_fee, match_price, false ) ); + + // Update bitasset data + modify( bitasset, [&call_receives,&call_pays]( asset_bitasset_data_object& obj ){ + obj.individual_settlement_debt -= call_receives.amount; + obj.individual_settlement_fund -= call_pays.amount; + }); + + // Update the taker order + // Note: CORE asset in settled debt is not counted in account_stats.total_core_in_orders + if( taker_filled ) + remove( taker ); + else + { + modify( taker, [&bitasset]( limit_order_object& obj ) { + // Note: for simplicity, only update price when necessary + asset settled_debt( bitasset.individual_settlement_debt, obj.receive_asset_id() ); + obj.for_sale = settled_debt.multiply_and_round_up( obj.sell_price ).amount; + if( obj.for_sale > bitasset.individual_settlement_fund ) // be defensive, maybe unnecessary + { // GCOVR_EXCL_START + wlog( "Unexpected scene: obj.for_sale > bitasset.individual_settlement_fund" ); + obj.for_sale = bitasset.individual_settlement_fund; + obj.sell_price = ~bitasset.get_individual_settlement_price(); + } // GCOVR_EXCL_STOP + // Note: filled_amount is not updated, but it should be fine + }); + } + + // seller, pays, receives, ... + bool maker_filled = fill_limit_order( maker, call_receives, order_receives, true, match_price, true ); + + match_result_type result = get_match_result( taker_filled, maker_filled ); + return result; +} + + database::match_result_type database::match( const limit_order_object& bid, const call_order_object& ask, const price& match_price, const asset_bitasset_data_object& bitasset, @@ -1460,7 +1653,133 @@ asset database::match_impl( const force_settlement_object& settle, cancel_settle_order( settle ); return call_receives; -} FC_CAPTURE_AND_RETHROW( (p_match_price)(max_settlement)(p_fill_price)(is_margin_call)(settle_is_taker) ) } +} FC_CAPTURE_AND_RETHROW( (p_match_price)(max_settlement)(p_fill_price) // GCOVR_EXCL_LINE + (is_margin_call)(settle_is_taker) ) } // GCOVR_EXCL_LINE + +optional database::process_limit_order_on_fill( const limit_order_object& order, + const asset& order_receives ) +{ + optional result; + if( order.on_fill.empty() ) + return result; + + const auto& take_profit_action = order.get_take_profit_action(); + + fc::uint128_t amount128( order_receives.amount.value ); + amount128 *= take_profit_action.size_percent; + amount128 += (GRAPHENE_100_PERCENT - 1); // Round up + amount128 /= GRAPHENE_100_PERCENT; + // GCOVR_EXCL_START + // Defensive code, should not happen + if( amount128 <= 0 ) + return result; + // GCOVR_EXCL_STOP + + asset for_sale( static_cast( amount128 ), order_receives.asset_id ); + + if( order.take_profit_order_id.valid() ) // Update existing take profit order + { + limit_order_update_operation op; + op.seller = order.seller; + op.order = *order.take_profit_order_id; + op.delta_amount_to_sell = for_sale; + + if( ( time_point_sec::maximum() - take_profit_action.expiration_seconds ) > head_block_time() ) + op.new_expiration = head_block_time() + take_profit_action.expiration_seconds; + else + op.new_expiration = time_point_sec::maximum(); + + try + { + if( take_profit_action.fee_asset_id == asset_id_type() ) + op.fee = current_fee_schedule().calculate_fee( op ); + else + op.fee = current_fee_schedule().calculate_fee( op, + take_profit_action.fee_asset_id(*this).options.core_exchange_rate ); // This may throw + + if( *order.take_profit_order_id > order.get_id() ) //The linked take profit order was generated by this order + { + // Update order price + const auto& take_profit_order = (*order.take_profit_order_id)(*this); + for_sale.amount += take_profit_order.for_sale; + auto sell_price = (~order.sell_price) * ratio_type( GRAPHENE_100_PERCENT, + int32_t(GRAPHENE_100_PERCENT) + take_profit_action.spread_percent ); + auto new_min_to_receive = for_sale.multiply_and_round_up( sell_price ); // This may throw + op.new_price = for_sale / new_min_to_receive; + } + // else do not update order price + + // GCOVR_EXCL_START + // Defensive code, should not fail + FC_ASSERT( !op.new_price || ( ~(*op.new_price) > order.sell_price ), + "Internal error: the take profit order should not match the current order" ); + // GCOVR_EXCL_STOP + + transaction_evaluation_state eval_state(this); + eval_state.skip_limit_order_price_check = true; + + try_push_virtual_operation( eval_state, op ); + } + catch( const fc::exception& e ) + { + // We can in fact get here + // e.g. if the selling or receiving asset issuer blacklisted the account, + // or no sufficient balance to pay fees, or undo sessions nested too deeply + wlog( "At block ${n}, failed to process on_fill for limit order ${order}, " + "automatic action (maybe incomplete) was ${op}, exception was ${e}", + ("op", operation(op))("order", order) + ("n", head_block_num())("e", e.to_detail_string()) ); + } + } + else // Create a new take profit order + { + limit_order_create_operation op; + op.seller = order.seller; + op.amount_to_sell = for_sale; + if( ( time_point_sec::maximum() - take_profit_action.expiration_seconds ) > head_block_time() ) + op.expiration = head_block_time() + take_profit_action.expiration_seconds; + else + op.expiration = time_point_sec::maximum(); + if( take_profit_action.repeat ) + op.extensions.value.on_fill = order.on_fill; + + try + { + if( take_profit_action.fee_asset_id == asset_id_type() ) + op.fee = current_fee_schedule().calculate_fee( op ); + else + op.fee = current_fee_schedule().calculate_fee( op, + take_profit_action.fee_asset_id(*this).options.core_exchange_rate ); // This may throw + + auto sell_price = (~order.sell_price) * ratio_type( GRAPHENE_100_PERCENT, + int32_t(GRAPHENE_100_PERCENT) + take_profit_action.spread_percent ); + op.min_to_receive = for_sale.multiply_and_round_up( sell_price ); // This may throw + + // GCOVR_EXCL_START + // Defensive code, should not fail + FC_ASSERT( ~op.get_price() > order.sell_price, + "Internal error: the take profit order should not match the current order" ); + // GCOVR_EXCL_STOP + + transaction_evaluation_state eval_state(this); + + auto op_result = try_push_virtual_operation( eval_state, op ); + result = limit_order_id_type( op_result.get() ); + } + catch( const fc::exception& e ) + { + // We can in fact get here + // e.g. if the selling or receiving asset issuer blacklisted the account, + // or no sufficient balance to pay fees, or undo sessions nested too deeply + wlog( "At block ${n}, failed to process on_fill for limit order ${order}, " + "automatic action (maybe incomplete) was ${op}, exception was ${e}", + ("op", operation(op))("order", order) + ("n", head_block_num())("e", e.to_detail_string()) ); + } + } + + return result; +} bool database::fill_limit_order( const limit_order_object& order, const asset& pays, const asset& receives, bool cull_if_small, const price& fill_price, const bool is_maker) @@ -1468,16 +1787,19 @@ bool database::fill_limit_order( const limit_order_object& order, const asset& p if( head_block_time() < HARDFORK_555_TIME ) cull_if_small = true; + // GCOVR_EXCL_START + // Defensive code, normally none of these should fail FC_ASSERT( order.amount_for_sale().asset_id == pays.asset_id ); FC_ASSERT( pays.asset_id != receives.asset_id ); + // GCOVR_EXCL_STOP const account_object& seller = order.seller(*this); const auto issuer_fees = pay_market_fees(&seller, receives.asset_id(*this), receives, is_maker); - pay_order( seller, receives - issuer_fees, pays ); + auto order_receives = receives - issuer_fees; + pay_order( seller, order_receives, pays ); - assert( pays.asset_id != receives.asset_id ); push_applied_operation( fill_order_operation( order.id, order.seller, pays, receives, issuer_fees, fill_price, is_maker ) ); @@ -1550,23 +1872,35 @@ bool database::fill_limit_order( const limit_order_object& order, const asset& p } } + // Process on_fill for order_receives + optional new_take_profit_order_id = process_limit_order_on_fill( order, order_receives ); + + // If this order is fully filled if( pays == order.amount_for_sale() ) { - remove( order ); + cleanup_and_remove_limit_order( order ); return true; } - else + + // This order is partially filled + if( new_take_profit_order_id.valid() ) // A new take profit order is created, link this order to it { - modify( order, [&pays]( limit_order_object& b ) { - b.for_sale -= pays.amount; - b.deferred_fee = 0; - b.deferred_paid_fee.amount = 0; - }); - if( cull_if_small ) - return maybe_cull_small_order( *this, order ); - return false; + modify( (*new_take_profit_order_id)(*this), [&order]( limit_order_object& loo ) { + loo.take_profit_order_id = order.get_id(); + }); } -} FC_CAPTURE_AND_RETHROW( (pays)(receives) ) } + modify( order, [&pays,&new_take_profit_order_id]( limit_order_object& b ) { + b.for_sale -= pays.amount; + b.filled_amount += pays.amount.value; + b.deferred_fee = 0; + b.deferred_paid_fee.amount = 0; + if( new_take_profit_order_id.valid() ) // A new take profit order is created, link it to this order + b.take_profit_order_id = *new_take_profit_order_id; + }); + if( cull_if_small ) + return maybe_cull_small_order( *this, order ); + return false; +} FC_CAPTURE_AND_RETHROW( (pays)(receives) ) } // GCOVR_EXCL_LINE /*** * @brief fill a call order in the specified amounts @@ -1649,7 +1983,7 @@ bool database::fill_call_order( const call_order_object& order, const asset& pay remove( order ); return collateral_freed.valid(); -} FC_CAPTURE_AND_RETHROW( (pays)(receives) ) } +} FC_CAPTURE_AND_RETHROW( (pays)(receives) ) } // GCOVR_EXCL_LINE /*** * @brief fullfill a settle order in the specified amounts @@ -1722,7 +2056,7 @@ bool database::fill_settle_order( const force_settlement_object& settle, const a return filled; -} FC_CAPTURE_AND_RETHROW( (pays)(receives) ) } +} FC_CAPTURE_AND_RETHROW( (pays)(receives) ) } // GCOVR_EXCL_LINE /** * Starting with the least collateralized orders, fill them if their @@ -1855,14 +2189,17 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa { // check for blackswan first // TODO perhaps improve performance by passing in iterators bool settled_some = check_for_blackswan( mia, enable_black_swan, &bitasset ); - if( bitasset.has_settlement() ) + if( bitasset.is_globally_settled() ) return margin_called; if( settled_some ) // which implies that BSRM is individual settlement to fund or to order { call_collateral_itr = call_collateral_index.lower_bound( call_min ); - if( call_collateral_itr == call_collateral_end ) + if( call_collateral_itr == call_collateral_end ) // no call order left + { + check_settled_debt_order( bitasset ); return true; + } margin_called = true; if( bsrm_type::individual_settlement_to_fund == bsrm ) limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); @@ -1876,7 +2213,10 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa ( after_hardfork_436 && bitasset.current_feed.settlement_price > ~call_order.call_price ) : ( bitasset.current_maintenance_collateralization < call_order.collateralization() ); if( feed_protected ) + { + check_settled_debt_order( bitasset ); return margin_called; + } // match call orders with limit orders if( limit_itr != limit_end ) @@ -2064,6 +2404,8 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa return margin_called; // If no force settlements, we return + // Note: there is no matching limit order due to MSSR, or no limit order at all, + // in either case, the settled debt order can't be matched auto settle_itr = settlement_index.lower_bound( bitasset.asset_id ); if( settle_itr == settlement_index.end() || settle_itr->balance.asset_id != bitasset.asset_id ) return margin_called; @@ -2085,17 +2427,21 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa } // else : no more force settlements, or feed protected, both will be handled in the next loop } // while there exists a call order + check_settled_debt_order( bitasset ); return margin_called; -} FC_CAPTURE_AND_RETHROW() } +} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE bool database::match_force_settlements( const asset_bitasset_data_object& bitasset ) { // Defensive checks auto maint_time = get_dynamic_global_properties().next_maintenance_time; + // GCOVR_EXCL_START + // Defensive code, normally none of these should fail FC_ASSERT( HARDFORK_CORE_2481_PASSED( maint_time ), "Internal error: hard fork core-2481 not passed" ); FC_ASSERT( !bitasset.is_prediction_market, "Internal error: asset is a prediction market" ); - FC_ASSERT( !bitasset.has_settlement(), "Internal error: asset is globally settled already" ); + FC_ASSERT( !bitasset.is_globally_settled(), "Internal error: asset is globally settled already" ); FC_ASSERT( !bitasset.current_feed.settlement_price.is_null(), "Internal error: no sufficient price feeds" ); + // GCOVR_EXCL_STOP auto head_time = head_block_time(); bool after_core_hardfork_2582 = HARDFORK_CORE_2582_PASSED( head_time ); // Price feed issues @@ -2155,6 +2501,47 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass return false; } +void database::check_settled_debt_order( const asset_bitasset_data_object& bitasset ) +{ + const auto& head_time = head_block_time(); + bool after_core_hardfork_2591 = HARDFORK_CORE_2591_PASSED( head_time ); // Tighter peg (fill debt order at MCOP) + if( !after_core_hardfork_2591 ) + return; + + using bsrm_type = bitasset_options::black_swan_response_type; + const auto bsrm = bitasset.get_black_swan_response_method(); + if( bsrm_type::individual_settlement_to_order != bsrm ) + return; + + const limit_order_object* limit_ptr = find_settled_debt_order( bitasset.asset_id ); + if( !limit_ptr ) + return; + + const limit_order_index& limit_index = get_index_type(); + const auto& limit_price_index = limit_index.indices().get(); + + // Looking for limit orders selling the most USD for the least CORE. + auto max_price = price::max( bitasset.asset_id, bitasset.options.short_backing_asset ); + // Stop when limit orders are selling too little USD for too much CORE. + auto min_price = ~limit_ptr->sell_price; + + // NOTE limit_price_index is sorted from greatest to least + auto limit_itr = limit_price_index.lower_bound( max_price ); + auto limit_end = limit_price_index.upper_bound( min_price ); + + bool finished = false; // whether the settled debt order is gone + while( !finished && limit_itr != limit_end ) + { + const limit_order_object& matching_limit_order = *limit_itr; + ++limit_itr; + price old_price = limit_ptr->sell_price; + finished = ( match_settled_debt_limit( *limit_ptr, matching_limit_order, matching_limit_order.sell_price ) + != match_result_type::only_maker_filled ); + if( !finished && old_price != limit_ptr->sell_price ) + limit_end = limit_price_index.upper_bound( ~limit_ptr->sell_price ); + } +} + void database::pay_order( const account_object& receiver, const asset& receives, const asset& pays ) { if( pays.asset_id == asset_id_type() ) diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 1e43bcdc00..c2dce4d491 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -56,6 +56,10 @@ struct get_impacted_account_visitor { _impacted.insert( op.fee_payer() ); // seller } + void operator()(const limit_order_update_operation& op) + { + _impacted.insert(op.fee_payer()); // seller + } void operator()( const limit_order_cancel_operation& op ) { _impacted.insert( op.fee_payer() ); // fee_paying_account @@ -326,6 +330,10 @@ struct get_impacted_account_visitor { _impacted.insert( op.fee_payer() ); // account } + void operator()( const liquidity_pool_update_operation& op ) + { + _impacted.insert( op.fee_payer() ); // account + } void operator()( const liquidity_pool_deposit_operation& op ) { _impacted.insert( op.fee_payer() ); // account @@ -383,6 +391,10 @@ struct get_impacted_account_visitor _impacted.insert( op.offer_owner ); _impacted.insert( op.borrower ); } + void operator()( const credit_deal_update_operation& op ) + { + _impacted.insert( op.fee_payer() ); // account + } }; } // namespace detail @@ -644,6 +656,6 @@ void database::notify_changed_objects() } catch( const graphene::chain::plugin_exception& e ) { elog( "Caught plugin exception: ${e}", ("e", e.to_detail_string() ) ); throw; -} FC_CAPTURE_AND_LOG( (0) ) } +} FC_CAPTURE_AND_LOG( (0) ) } // GCOVR_EXCL_LINE } } // namespace graphene::chain diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index e13b51a9dc..9e52b933ed 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -150,7 +150,7 @@ void database::clear_expired_transactions() const auto& dedupe_index = transaction_idx.indices().get(); while( (!dedupe_index.empty()) && (head_block_time() > dedupe_index.begin()->trx.expiration) ) transaction_idx.remove(*dedupe_index.begin()); -} FC_CAPTURE_AND_RETHROW() } +} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE void database::clear_expired_proposals() { @@ -222,6 +222,51 @@ static optional get_derived_current_feed_price( const database& db, return result; } +// Helper function to update the limit order which is the individual settlement fund of the specified asset +static void update_settled_debt_order( database& db, const asset_bitasset_data_object& bitasset ) +{ + // To avoid unexpected price fluctuations, do not update the order if no sufficient price feeds + if( bitasset.current_feed.settlement_price.is_null() ) + return; + + const limit_order_object* limit_ptr = db.find_settled_debt_order( bitasset.asset_id ); + if( !limit_ptr ) + return; + + bool sell_all = true; + share_type for_sale; + + // Note: bitasset.get_margin_call_order_price() is in debt/collateral + price sell_price = ~bitasset.get_margin_call_order_price(); + asset settled_debt( bitasset.individual_settlement_debt, limit_ptr->receive_asset_id() ); + try + { + for_sale = settled_debt.multiply_and_round_up( sell_price ).amount; // may overflow + if( for_sale <= bitasset.individual_settlement_fund ) // "=" is for the consistency of order matching logic + sell_all = false; + } + catch( const fc::exception& e ) // catch the overflow + { + // do nothing + dlog( e.to_detail_string() ); + } + + // TODO Potential optimization: to avoid unnecessary database update, check before update + db.modify( *limit_ptr, [sell_all, &sell_price, &for_sale, &bitasset]( limit_order_object& obj ) + { + if( sell_all ) + { + obj.for_sale = bitasset.individual_settlement_fund; + obj.sell_price = ~bitasset.get_individual_settlement_price(); + } + else + { + obj.for_sale = for_sale; + obj.sell_price = sell_price; + } + } ); +} + void database::update_bitasset_current_feed( const asset_bitasset_data_object& bitasset, bool skip_median_update ) { // For better performance, if nothing to update, we return @@ -245,13 +290,14 @@ void database::update_bitasset_current_feed( const asset_bitasset_data_object& b } } + const auto& head_time = head_block_time(); + // We need to update the database - modify( bitasset, [this, skip_median_update, &new_current_feed_price, &bsrm] + modify( bitasset, [this, skip_median_update, &head_time, &new_current_feed_price, &bsrm] ( asset_bitasset_data_object& abdo ) { if( !skip_median_update ) { - const auto& head_time = head_block_time(); const auto& maint_time = get_dynamic_global_properties().next_maintenance_time; abdo.update_median_feeds( head_time, maint_time ); abdo.current_feed = abdo.median_feed; @@ -261,6 +307,14 @@ void database::update_bitasset_current_feed( const asset_bitasset_data_object& b if( new_current_feed_price.valid() ) abdo.current_feed.settlement_price = *new_current_feed_price; } ); + + // Update individual settlement order price + if( !skip_median_update + && bsrm_type::individual_settlement_to_order == bsrm + && HARDFORK_CORE_2591_PASSED( head_time ) ) // Tighter peg (fill individual settlement order at MCOP) + { + update_settled_debt_order( *this, bitasset ); + } } void database::clear_expired_orders() @@ -289,7 +343,7 @@ void database::clear_expired_orders() check_call_orders( quote_asset( *this ) ); } } -} FC_CAPTURE_AND_RETHROW() } +} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE void database::clear_expired_force_settlements() { try { @@ -351,7 +405,7 @@ void database::clear_expired_force_settlements() const asset_object& mia_object = *mia_object_ptr; const asset_bitasset_data_object& mia = *mia_ptr; - if( mia.has_settlement() ) + if( mia.is_globally_settled() ) { ilog( "Canceling a force settlement because of black swan" ); cancel_settle_order( settle_order ); @@ -471,7 +525,7 @@ void database::clear_expired_force_settlements() }); } } -} FC_CAPTURE_AND_RETHROW() } +} FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE void database::update_expired_feeds() { @@ -715,6 +769,63 @@ void database::update_credit_offers_and_deals() { const credit_deal_object& deal = *deal_itr; + // Process automatic repayment + // Note: an automatic repayment may fail, in which case we consider the credit deal past due without repayment + using repay_type = credit_deal_auto_repayment_type; + if( static_cast(repay_type::no_auto_repayment) != deal.auto_repay ) + { + credit_deal_repay_operation op; + op.account = deal.borrower; + op.deal_id = deal.get_id(); + // Amounts + // Note: the result can be larger than 64 bit + auto required_fee = ( ( ( fc::uint128_t( deal.debt_amount.value ) * deal.fee_rate ) + + GRAPHENE_FEE_RATE_DENOM ) - 1 ) / GRAPHENE_FEE_RATE_DENOM; // Round up + fc::uint128_t total_required = required_fee + deal.debt_amount.value; + auto balance = get_balance( deal.borrower, deal.debt_asset ); + if( static_cast(repay_type::only_full_repayment) == deal.auto_repay + || fc::uint128_t( balance.amount.value ) >= total_required ) + { // if only full repayment or account balance is sufficient + op.repay_amount = asset( deal.debt_amount, deal.debt_asset ); + op.credit_fee = asset( static_cast( required_fee ), deal.debt_asset ); + } + else // Allow partial repayment + { + fc::uint128_t debt_to_repay = ( fc::uint128_t( balance.amount.value ) * GRAPHENE_FEE_RATE_DENOM ) + / ( GRAPHENE_FEE_RATE_DENOM + deal.fee_rate ); // Round down + fc::uint128_t collateral_to_release = ( debt_to_repay * deal.collateral_amount.value ) + / deal.debt_amount.value; // Round down + debt_to_repay = ( ( ( collateral_to_release * deal.debt_amount.value ) + deal.collateral_amount.value ) + - 1 ) / deal.collateral_amount.value; // Round up + fc::uint128_t fee_to_pay = ( ( ( debt_to_repay * deal.fee_rate ) + + GRAPHENE_FEE_RATE_DENOM ) - 1 ) / GRAPHENE_FEE_RATE_DENOM; // Round up + op.repay_amount = asset( static_cast( debt_to_repay ), deal.debt_asset ); + op.credit_fee = asset( static_cast( fee_to_pay ), deal.debt_asset ); + } + + auto deal_copy = deal; // Make a copy for logging + + transaction_evaluation_state eval_state(this); + eval_state.skip_fee_schedule_check = true; + + try + { + try_push_virtual_operation( eval_state, op ); + } + catch( const fc::exception& e ) + { + // We can in fact get here, + // e.g. if the debt asset issuer blacklisted the account, or account balance is insufficient + wlog( "Automatic repayment ${op} for credit deal ${credit_deal} failed at block ${n}; " + "account balance was ${balance}; exception was ${e}", + ("op", op)("credit_deal", deal_copy) + ("n", head_block_num())("balance", balance)("e", e.to_detail_string()) ); + } + + if( !find( op.deal_id ) ) // The credit deal is fully repaid + continue; + } + // Update offer // Note: offer balance can be zero after updated. TODO remove zero-balance offers after a period const credit_offer_object& offer = deal.offer_id(*this); diff --git a/libraries/chain/evaluator.cpp b/libraries/chain/evaluator.cpp index 42b7f82c1d..818770e9ff 100644 --- a/libraries/chain/evaluator.cpp +++ b/libraries/chain/evaluator.cpp @@ -79,28 +79,23 @@ database& generic_evaluator::db()const { return trx_state->db(); } void generic_evaluator::convert_fee() { - if( !trx_state->skip_fee ) { - if( fee_asset->get_id() != asset_id_type() ) - { - db().modify(*fee_asset_dyn_data, [this](asset_dynamic_data_object& d) { - d.accumulated_fees += fee_from_account.amount; - d.fee_pool -= core_fee_paid; - }); - } + if( fee_asset->get_id() != asset_id_type() ) + { + db().modify(*fee_asset_dyn_data, [this](asset_dynamic_data_object& d) { + d.accumulated_fees += fee_from_account.amount; + d.fee_pool -= core_fee_paid; + }); } } void generic_evaluator::pay_fee() { try { - if( !trx_state->skip_fee ) { - database& d = db(); - /// TODO: db().pay_fee( account_id, core_fee ); - d.modify(*fee_paying_account_statistics, [&](account_statistics_object& s) - { - s.pay_fee( core_fee_paid, d.get_global_properties().parameters.cashback_vesting_threshold ); - }); - } - } FC_CAPTURE_AND_RETHROW() } + database& d = db(); + d.modify(*fee_paying_account_statistics, [this,&d](account_statistics_object& s) + { + s.pay_fee( core_fee_paid, d.get_global_properties().parameters.cashback_vesting_threshold ); + }); + } FC_CAPTURE_AND_RETHROW() } // GCOVR_EXCL_LINE void generic_evaluator::pay_fba_fee( uint64_t fba_id ) { diff --git a/libraries/chain/exceptions.cpp b/libraries/chain/exceptions.cpp index 05b3712583..1db241def5 100644 --- a/libraries/chain/exceptions.cpp +++ b/libraries/chain/exceptions.cpp @@ -84,6 +84,10 @@ namespace graphene { namespace chain { GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( insufficient_balance, limit_order_create, 6, "Insufficient balance" ) + GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( limit_order_update ); + GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( nonexist_order, limit_order_update, 1, "Order does not exist" ) + GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( owner_mismatch, limit_order_update, 2, "Order owned by someone else" ) + GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( limit_order_cancel ); GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( nonexist_order, limit_order_cancel, 1, "Order does not exist" ) GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( owner_mismatch, limit_order_cancel, 2, "Order owned by someone else" ) diff --git a/libraries/chain/hardfork.d/CORE_1604.hf b/libraries/chain/hardfork.d/CORE_1604.hf new file mode 100644 index 0000000000..f5d73b1074 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_1604.hf @@ -0,0 +1,5 @@ +// bitshares-core issue #1604 Operation to modify existing limit order +#ifndef HARDFORK_CORE_1604_TIME +#define HARDFORK_CORE_1604_TIME (fc::time_point_sec( 1700143200 )) // Thursday, November 16, 2023 14:00:00 UTC +#define HARDFORK_CORE_1604_PASSED(head_block_time) (head_block_time >= HARDFORK_CORE_1604_TIME) +#endif diff --git a/libraries/chain/hardfork.d/CORE_2535.hf b/libraries/chain/hardfork.d/CORE_2535.hf new file mode 100644 index 0000000000..b0d3dbc479 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_2535.hf @@ -0,0 +1,5 @@ +// bitshares-core issue #2535 Simple Order-Sends-Order (OSO) +#ifndef HARDFORK_CORE_2535_TIME +#define HARDFORK_CORE_2535_TIME (fc::time_point_sec( 1700143200 )) // Thursday, November 16, 2023 14:00:00 UTC +#define HARDFORK_CORE_2535_PASSED(head_block_time) (head_block_time >= HARDFORK_CORE_2535_TIME) +#endif diff --git a/libraries/chain/hardfork.d/CORE_2587.hf b/libraries/chain/hardfork.d/CORE_2587.hf new file mode 100644 index 0000000000..250fd3c7ef --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_2587.hf @@ -0,0 +1,5 @@ +// bitshares-core issue #2587 settle more than total debt in individual settlement fund when no sufficient price feeds +#ifndef HARDFORK_CORE_2587_TIME +#define HARDFORK_CORE_2587_TIME (fc::time_point_sec( 1700143200 )) // Thursday, November 16, 2023 14:00:00 UTC +#define HARDFORK_CORE_2587_PASSED(head_block_time) (head_block_time >= HARDFORK_CORE_2587_TIME) +#endif diff --git a/libraries/chain/hardfork.d/CORE_2591.hf b/libraries/chain/hardfork.d/CORE_2591.hf new file mode 100644 index 0000000000..6fe866edb8 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_2591.hf @@ -0,0 +1,5 @@ +// bitshares-core issue #2591 Tighter peg when collateral price rises and settlement fund or settlement order exists +#ifndef HARDFORK_CORE_2591_TIME +#define HARDFORK_CORE_2591_TIME (fc::time_point_sec( 1700143200 )) // Thursday, November 16, 2023 14:00:00 UTC +#define HARDFORK_CORE_2591_PASSED(head_block_time) (head_block_time >= HARDFORK_CORE_2591_TIME) +#endif diff --git a/libraries/chain/hardfork.d/CORE_2595.hf b/libraries/chain/hardfork.d/CORE_2595.hf new file mode 100644 index 0000000000..46a8ef4983 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_2595.hf @@ -0,0 +1,5 @@ +// bitshares-core issue #2595 Credit deal auto-repayment +#ifndef HARDFORK_CORE_2595_TIME +#define HARDFORK_CORE_2595_TIME (fc::time_point_sec( 1700143200 )) // Thursday, November 16, 2023 14:00:00 UTC +#define HARDFORK_CORE_2595_PASSED(head_block_time) (head_block_time >= HARDFORK_CORE_2595_TIME) +#endif diff --git a/libraries/chain/hardfork.d/CORE_2604.hf b/libraries/chain/hardfork.d/CORE_2604.hf new file mode 100644 index 0000000000..6d199ec145 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_2604.hf @@ -0,0 +1,5 @@ +// bitshares-core issue #2604 Allow updating liquidity pool fee rates with certain restrictions +#ifndef HARDFORK_CORE_2604_TIME +#define HARDFORK_CORE_2604_TIME (fc::time_point_sec( 1700143200 )) // Thursday, November 16, 2023 14:00:00 UTC +#define HARDFORK_CORE_2604_PASSED(head_block_time) (head_block_time >= HARDFORK_CORE_2604_TIME) +#endif diff --git a/libraries/chain/include/graphene/chain/asset_evaluator.hpp b/libraries/chain/include/graphene/chain/asset_evaluator.hpp index 8b35585f02..b4a1a0d209 100644 --- a/libraries/chain/include/graphene/chain/asset_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/asset_evaluator.hpp @@ -36,13 +36,13 @@ namespace graphene { namespace chain { public: using operation_type = asset_create_operation; - void_result do_evaluate( const asset_create_operation& o ); - object_id_type do_apply( const asset_create_operation& o ); + void_result do_evaluate( const asset_create_operation& o ) const; + object_id_type do_apply( const asset_create_operation& o ) const; /** override the default behavior defined by generic_evalautor which is to * post the fee to fee_paying_account_stats.pending_fees */ - virtual void pay_fee() override; + void pay_fee() override; private: bool fee_is_odd; }; @@ -52,8 +52,9 @@ namespace graphene { namespace chain { public: using operation_type = asset_issue_operation; void_result do_evaluate( const asset_issue_operation& o ); - void_result do_apply( const asset_issue_operation& o ); + void_result do_apply( const asset_issue_operation& o ) const; + private: const asset_dynamic_data_object* asset_dyn_data = nullptr; const account_object* to_account = nullptr; }; @@ -63,8 +64,9 @@ namespace graphene { namespace chain { public: using operation_type = asset_reserve_operation; void_result do_evaluate( const asset_reserve_operation& o ); - void_result do_apply( const asset_reserve_operation& o ); + void_result do_apply( const asset_reserve_operation& o ) const; + private: const asset_dynamic_data_object* asset_dyn_data = nullptr; const account_object* from_account = nullptr; }; @@ -78,6 +80,7 @@ namespace graphene { namespace chain { void_result do_evaluate( const asset_update_operation& o ); void_result do_apply( const asset_update_operation& o ); + private: const asset_object* asset_to_update = nullptr; const asset_bitasset_data_object* bitasset_data = nullptr; }; @@ -90,6 +93,7 @@ namespace graphene { namespace chain { void_result do_evaluate( const asset_update_issuer_operation& o ); void_result do_apply( const asset_update_issuer_operation& o ); + private: const asset_object* asset_to_update = nullptr; }; @@ -116,6 +120,7 @@ namespace graphene { namespace chain { void_result do_evaluate( const operation_type& o ); void_result do_apply( const operation_type& o ) const; + private: const asset_object* asset_to_update = nullptr; }; @@ -125,8 +130,9 @@ namespace graphene { namespace chain { using operation_type = asset_fund_fee_pool_operation; void_result do_evaluate(const asset_fund_fee_pool_operation& op); - void_result do_apply(const asset_fund_fee_pool_operation& op); + void_result do_apply(const asset_fund_fee_pool_operation& op) const; + private: const asset_dynamic_data_object* asset_dyn_data = nullptr; }; @@ -138,6 +144,7 @@ namespace graphene { namespace chain { void_result do_evaluate(const operation_type& op); void_result do_apply(const operation_type& op); + private: const asset_object* asset_to_settle = nullptr; }; class asset_settle_evaluator : public evaluator @@ -174,6 +181,7 @@ namespace graphene { namespace chain { void_result do_evaluate( const asset_claim_fees_operation& o ); void_result do_apply( const asset_claim_fees_operation& o ); + private: const asset_object* container_asset = nullptr; const asset_dynamic_data_object* container_ddo = nullptr; }; diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 66938a2390..38a31daffd 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -296,12 +296,15 @@ namespace graphene { namespace chain { share_type max_force_settlement_volume(share_type current_supply)const; /// @return true if the bitasset has been globally settled, false otherwise - bool has_settlement()const { return !settlement_price.is_null(); } + bool is_globally_settled()const { return !settlement_price.is_null(); } /** * In the event of global settlement, all margin positions * are settled with the siezed collateral being moved into the settlement fund. From this * point on forced settlement occurs immediately when requested, using the settlement price and fund. + * + * @note After the core-2591 hardfork, forced settlements may be paid at the margin call order price (MCOP) + * when applicable, but are not limited to the settlement price stated here. */ ///@{ /// Price at which force settlements of a globally settled asset will occur @@ -311,17 +314,29 @@ namespace graphene { namespace chain { ///@} /// The individual settlement pool. - /// In the event of individual settlements to fund, debt and collateral of the margin positions which got - /// settled are moved here. + /// In the event of individual settlements (to fund or to order), debt and collateral of the margin positions + /// which got settled are moved here. + /// * For individual settlement to fund, collateral assets in the pool can only be retrieved through + /// forced settlements. + /// * For individual settlement to order, collateral assets in the pool can only be retrieved through + /// limit orders. ///@{ /// Amount of debt due to individual settlements share_type individual_settlement_debt; - /// Amount of collateral which is available for force settlement due to individual settlements + /// Amount of collateral due to individual settlements share_type individual_settlement_fund; ///@} - /// @return true if the individual settlement pool is not empty, false otherwise - bool has_individual_settlement()const { return ( individual_settlement_debt != 0 ); } + /// @return true if the individual settlement pool is not empty and the bitasset's black swan response method + /// (BSRM) is @ref + /// graphene::protocol::bitasset_options::black_swan_response_type::individual_settlement_to_fund, + /// false otherwise + bool is_individually_settled_to_fund()const + { + using bsrm_type = bitasset_options::black_swan_response_type; + return ( ( individual_settlement_debt != 0 ) && + ( bsrm_type::individual_settlement_to_fund == get_black_swan_response_method() ) ); + } /// Get the price of the individual settlement pool price get_individual_settlement_price() const diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 5390eb2270..24bdbfa55e 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -32,7 +32,7 @@ #define GRAPHENE_MAX_NESTED_OBJECTS (200) -const std::string GRAPHENE_CURRENT_DB_VERSION = "20220930"; +const std::string GRAPHENE_CURRENT_DB_VERSION = "20230906"; #define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT 4 #define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT 3 diff --git a/libraries/chain/include/graphene/chain/credit_offer_evaluator.hpp b/libraries/chain/include/graphene/chain/credit_offer_evaluator.hpp index 359e3a686d..e5e730a190 100644 --- a/libraries/chain/include/graphene/chain/credit_offer_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/credit_offer_evaluator.hpp @@ -84,4 +84,15 @@ namespace graphene { namespace chain { const credit_deal_object* _deal = nullptr; }; + class credit_deal_update_evaluator : public evaluator + { + public: + using operation_type = credit_deal_update_operation; + + void_result do_evaluate( const credit_deal_update_operation& op ); + void_result do_apply( const credit_deal_update_operation& op ) const; + + const credit_deal_object* _deal = nullptr; + }; + } } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/credit_offer_object.hpp b/libraries/chain/include/graphene/chain/credit_offer_object.hpp index a8d427e7ce..79b1f13a07 100644 --- a/libraries/chain/include/graphene/chain/credit_offer_object.hpp +++ b/libraries/chain/include/graphene/chain/credit_offer_object.hpp @@ -113,6 +113,7 @@ class credit_deal_object : public abstract_object process_limit_order_on_fill( const limit_order_object& order, + const asset& order_receives ); void _cancel_bids_and_revive_mpa( const asset_object& bitasset, const asset_bitasset_data_object& bad ); bool check_for_blackswan( const asset_object& mia, bool enable_black_swan = true, const asset_bitasset_data_object* bitasset_ptr = nullptr ); @@ -396,6 +402,16 @@ namespace graphene { namespace chain { bool mute_exceptions = false, bool skip_matching_settle_orders = false ); + /** + * @brief Match the settled debt order of the specified asset as taker with other orders on the opposite side + * of the order book + * @param bitasset The bitasset data object + * + * Since the core-2591 hard fork, this function is called after processed all call orders in + * @ref check_call_orders(). + */ + void check_settled_debt_order( const asset_bitasset_data_object& bitasset ); + // Note: Ideally this should be private. // Now it is public because we use it in a non-member function in db_market.cpp . enum class match_result_type @@ -419,6 +435,8 @@ namespace graphene { namespace chain { const price& trade_price ); match_result_type match_limit_settled_debt( const limit_order_object& taker, const limit_order_object& maker, const price& trade_price ); + match_result_type match_settled_debt_limit( const limit_order_object& taker, const limit_order_object& maker, + const price& trade_price ); /*** * @brief Match limit order as taker to a call order as maker * @param taker the order that is removing liquidity from the book @@ -691,6 +709,11 @@ namespace graphene { namespace chain { void _apply_block( const signed_block& next_block ); processed_transaction _apply_transaction( const signed_transaction& trx ); + /// Validate, evaluate and apply a virtual operation using a temporary undo_database session, + /// if fail, rewind any changes made + operation_result try_push_virtual_operation( transaction_evaluation_state& eval_state, + const operation& op ); + ///Steps involved in applying a new block ///@{ @@ -803,8 +826,8 @@ namespace graphene { namespace chain { */ bool _opened = false; - // Counts nested proposal updates - uint32_t _push_proposal_nesting_depth = 0; + /// Counts nested undo sessions due to (for example) proposal updates or order-sends-order executions + uint32_t _undo_session_nesting_depth = 0; /// Tracks assets affected by bitshares-core issue #453 before hard fork #615 in one block flat_set _issue_453_affected_assets; diff --git a/libraries/chain/include/graphene/chain/exceptions.hpp b/libraries/chain/include/graphene/chain/exceptions.hpp index 07d2968c1f..54d723d9fa 100644 --- a/libraries/chain/include/graphene/chain/exceptions.hpp +++ b/libraries/chain/include/graphene/chain/exceptions.hpp @@ -138,6 +138,10 @@ namespace graphene { namespace chain { GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( receiving_asset_unauthorized, limit_order_create, 5 ) GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( insufficient_balance, limit_order_create, 6 ) + GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( limit_order_update ); + GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( nonexist_order, limit_order_update, 1 ) + GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( owner_mismatch, limit_order_update, 2 ) + GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( limit_order_cancel ); GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( nonexist_order, limit_order_cancel, 1 ) GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( owner_mismatch, limit_order_cancel, 2 ) diff --git a/libraries/chain/include/graphene/chain/hardfork_visitor.hpp b/libraries/chain/include/graphene/chain/hardfork_visitor.hpp index fb284f78db..321a9b9bb9 100644 --- a/libraries/chain/include/graphene/chain/hardfork_visitor.hpp +++ b/libraries/chain/include/graphene/chain/hardfork_visitor.hpp @@ -47,6 +47,7 @@ struct hardfork_visitor { using BSIP_40_ops = fc::typelist::list< protocol::custom_authority_create_operation, protocol::custom_authority_update_operation, protocol::custom_authority_delete_operation>; + using hf1604_ops = fc::typelist::list< protocol::limit_order_update_operation>; using hf2103_ops = fc::typelist::list< protocol::ticket_create_operation, protocol::ticket_update_operation>; using liquidity_pool_ops = fc::typelist::list< protocol::liquidity_pool_create_operation, @@ -54,6 +55,7 @@ struct hardfork_visitor { protocol::liquidity_pool_deposit_operation, protocol::liquidity_pool_withdraw_operation, protocol::liquidity_pool_exchange_operation >; + using liquidity_pool_update_op = fc::typelist::list< protocol::liquidity_pool_update_operation >; using samet_fund_ops = fc::typelist::list< protocol::samet_fund_create_operation, protocol::samet_fund_delete_operation, protocol::samet_fund_update_operation, @@ -65,6 +67,8 @@ struct hardfork_visitor { protocol::credit_offer_accept_operation, protocol::credit_deal_repay_operation, protocol::credit_deal_expired_operation >; + using credit_deal_update_op = fc::typelist::list< protocol::credit_deal_update_operation >; + fc::time_point_sec now; /// @note using head block time for all operations @@ -79,6 +83,9 @@ struct hardfork_visitor { std::enable_if_t(), bool> visit() { return HARDFORK_BSIP_40_PASSED(now); } template + std::enable_if_t(), bool> + visit() { return HARDFORK_CORE_1604_PASSED(now); } + template std::enable_if_t(), bool> visit() { return HARDFORK_CORE_2103_PASSED(now); } template @@ -90,6 +97,12 @@ struct hardfork_visitor { template std::enable_if_t(), bool> visit() { return HARDFORK_CORE_2362_PASSED(now); } + template + std::enable_if_t(), bool> + visit() { return HARDFORK_CORE_2595_PASSED(now); } + template + std::enable_if_t(), bool> + visit() { return HARDFORK_CORE_2604_PASSED(now); } /// @} /// typelist::runtime::dispatch adaptor diff --git a/libraries/chain/include/graphene/chain/liquidity_pool_evaluator.hpp b/libraries/chain/include/graphene/chain/liquidity_pool_evaluator.hpp index e7de47be76..b8e1b347d5 100644 --- a/libraries/chain/include/graphene/chain/liquidity_pool_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/liquidity_pool_evaluator.hpp @@ -35,34 +35,49 @@ namespace graphene { namespace chain { class liquidity_pool_create_evaluator : public evaluator { public: - typedef liquidity_pool_create_operation operation_type; + using operation_type = liquidity_pool_create_operation; void_result do_evaluate( const liquidity_pool_create_operation& op ); generic_operation_result do_apply( const liquidity_pool_create_operation& op ); + private: const asset_object* _share_asset = nullptr; }; class liquidity_pool_delete_evaluator : public evaluator { public: - typedef liquidity_pool_delete_operation operation_type; + using operation_type = liquidity_pool_delete_operation; void_result do_evaluate( const liquidity_pool_delete_operation& op ); - generic_operation_result do_apply( const liquidity_pool_delete_operation& op ); + generic_operation_result do_apply( const liquidity_pool_delete_operation& op ) const; + private: const liquidity_pool_object* _pool = nullptr; const asset_object* _share_asset = nullptr; }; + class liquidity_pool_update_evaluator : public evaluator + { + public: + using operation_type = liquidity_pool_update_operation; + + void_result do_evaluate( const liquidity_pool_update_operation& op ); + void_result do_apply( const liquidity_pool_update_operation& op ) const; + + private: + const liquidity_pool_object* _pool = nullptr; + }; + class liquidity_pool_deposit_evaluator : public evaluator { public: - typedef liquidity_pool_deposit_operation operation_type; + using operation_type = liquidity_pool_deposit_operation; void_result do_evaluate( const liquidity_pool_deposit_operation& op ); generic_exchange_operation_result do_apply( const liquidity_pool_deposit_operation& op ); + private: const liquidity_pool_object* _pool = nullptr; const asset_dynamic_data_object* _share_asset_dyn_data = nullptr; asset _account_receives; @@ -73,11 +88,12 @@ namespace graphene { namespace chain { class liquidity_pool_withdraw_evaluator : public evaluator { public: - typedef liquidity_pool_withdraw_operation operation_type; + using operation_type = liquidity_pool_withdraw_operation; void_result do_evaluate( const liquidity_pool_withdraw_operation& op ); generic_exchange_operation_result do_apply( const liquidity_pool_withdraw_operation& op ); + private: const liquidity_pool_object* _pool = nullptr; const asset_dynamic_data_object* _share_asset_dyn_data = nullptr; asset _pool_pays_a; @@ -89,11 +105,12 @@ namespace graphene { namespace chain { class liquidity_pool_exchange_evaluator : public evaluator { public: - typedef liquidity_pool_exchange_operation operation_type; + using operation_type = liquidity_pool_exchange_operation; void_result do_evaluate( const liquidity_pool_exchange_operation& op ); generic_exchange_operation_result do_apply( const liquidity_pool_exchange_operation& op ); + private: const liquidity_pool_object* _pool = nullptr; const asset_object* _pool_pays_asset = nullptr; const asset_object* _pool_receives_asset = nullptr; diff --git a/libraries/chain/include/graphene/chain/market_evaluator.hpp b/libraries/chain/include/graphene/chain/market_evaluator.hpp index f9d94ddd5f..f17c96d81b 100644 --- a/libraries/chain/include/graphene/chain/market_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/market_evaluator.hpp @@ -42,14 +42,14 @@ namespace graphene { namespace chain { void_result do_evaluate( const limit_order_create_operation& o ); object_id_type do_apply( const limit_order_create_operation& o ) const; - /** override the default behavior defined by generic_evalautor + /** override the default behavior defined by generic_evaluator */ - virtual void convert_fee() override; + void convert_fee() override; - /** override the default behavior defined by generic_evalautor which is to + /** override the default behavior defined by generic_evaluator which is to * post the fee to fee_paying_account_stats.pending_fees */ - virtual void pay_fee() override; + void pay_fee() override; private: share_type _deferred_fee = 0; @@ -59,6 +59,34 @@ namespace graphene { namespace chain { const asset_object* _receive_asset = nullptr; }; + class limit_order_update_evaluator : public evaluator + { + public: + using operation_type = limit_order_update_operation; + + void_result do_evaluate(const limit_order_update_operation& o); + void_result do_apply(const limit_order_update_operation& o); + + /** override the default behavior defined by generic_evaluator + */ + void convert_fee() override; + + /** override the default behavior defined by generic_evaluator which is to + * post the fee to fee_paying_account_stats.pending_fees + */ + void pay_fee() override; + + private: + void process_deferred_fee(); + /// Check if the linked take profit order is still compatible with the current order after update + bool is_linked_tp_order_compatible( const limit_order_update_operation& o ) const; + + share_type _deferred_fee; + asset _deferred_paid_fee; + const limit_order_object* _order = nullptr; + const account_statistics_object* _seller_acc_stats = nullptr; + }; + class limit_order_cancel_evaluator : public evaluator { public: @@ -68,7 +96,7 @@ namespace graphene { namespace chain { asset do_apply( const limit_order_cancel_operation& o ) const; private: - const limit_order_object* _order; + const limit_order_object* _order = nullptr; }; class call_order_update_evaluator : public evaluator diff --git a/libraries/chain/include/graphene/chain/market_object.hpp b/libraries/chain/include/graphene/chain/market_object.hpp index 724be9c25b..b044305851 100644 --- a/libraries/chain/include/graphene/chain/market_object.hpp +++ b/libraries/chain/include/graphene/chain/market_object.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -44,14 +45,28 @@ using namespace graphene::db; class limit_order_object : public abstract_object { public: - time_point_sec expiration; - account_id_type seller; - share_type for_sale; ///< asset id is sell_price.base.asset_id - price sell_price; + time_point_sec expiration; ///< When this limit order will expire + account_id_type seller; ///< Who is selling + share_type for_sale; ///< The amount for sale, asset id is sell_price.base.asset_id + price sell_price; ///< The seller's asking price + fc::uint128_t filled_amount = 0; ///< The amount that has been sold, asset id is sell_price.base.asset_id share_type deferred_fee; ///< fee converted to CORE asset deferred_paid_fee; ///< originally paid fee bool is_settled_debt = false; ///< Whether this order is an individual settlement fund + /// Automatic actions when the limit order is filled or partially filled + vector< limit_order_auto_action > on_fill; + + /// ID of the take profit limit order linked to this limit order + optional take_profit_order_id; + + /// Returns the configured automatic action that will create a take profit order when this limit order is filled + const create_take_profit_order_action& get_take_profit_action() const + { + FC_ASSERT( !on_fill.empty() ); // Normally it should not fail // GCOVR_EXCL_LINE + return on_fill.front().get(); + } + pair get_market()const { auto tmp = std::make_pair( sell_price.base.asset_id, sell_price.quote.asset_id ); diff --git a/libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp b/libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp index 53c6f3eb87..c00e977983 100644 --- a/libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp +++ b/libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp @@ -47,7 +47,7 @@ namespace chain { const signed_transaction* _trx = nullptr; database* _db = nullptr; bool _is_proposed_trx = false; - bool skip_fee = false; bool skip_fee_schedule_check = false; + bool skip_limit_order_price_check = false; // Used in limit_order_update_op }; } } // namespace graphene::chain diff --git a/libraries/chain/liquidity_pool_evaluator.cpp b/libraries/chain/liquidity_pool_evaluator.cpp index d0c6d14e9d..553ff3b897 100644 --- a/libraries/chain/liquidity_pool_evaluator.cpp +++ b/libraries/chain/liquidity_pool_evaluator.cpp @@ -59,7 +59,7 @@ void_result liquidity_pool_create_evaluator::do_evaluate(const liquidity_pool_cr "Current supply of the share asset needs to be zero" ); return void_result(); -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE generic_operation_result liquidity_pool_create_evaluator::do_apply(const liquidity_pool_create_operation& op) { try { @@ -81,7 +81,7 @@ generic_operation_result liquidity_pool_create_evaluator::do_apply(const liquidi }); return result; -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE void_result liquidity_pool_delete_evaluator::do_evaluate(const liquidity_pool_delete_operation& op) { try { @@ -96,9 +96,9 @@ void_result liquidity_pool_delete_evaluator::do_evaluate(const liquidity_pool_de FC_ASSERT( _share_asset->issuer == op.account, "The account is not the owner of the liquidity pool" ); return void_result(); -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE -generic_operation_result liquidity_pool_delete_evaluator::do_apply(const liquidity_pool_delete_operation& op) +generic_operation_result liquidity_pool_delete_evaluator::do_apply(const liquidity_pool_delete_operation& op) const { try { database& d = db(); generic_operation_result result; @@ -112,7 +112,45 @@ generic_operation_result liquidity_pool_delete_evaluator::do_apply(const liquidi d.remove( *_pool ); return result; -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE + +void_result liquidity_pool_update_evaluator::do_evaluate(const liquidity_pool_update_operation& op) +{ try { + const database& d = db(); + const auto block_time = d.head_block_time(); + + FC_ASSERT( HARDFORK_CORE_2604_PASSED(block_time), "Not allowed until the core-2604 hardfork" ); + + _pool = &op.pool(d); + + const asset_object* _share_asset = &_pool->share_asset(d); + + FC_ASSERT( _share_asset->issuer == op.account, "The account is not the owner of the liquidity pool" ); + + if( op.taker_fee_percent.valid() ) + { + FC_ASSERT( 0 == _pool->withdrawal_fee_percent + || ( op.withdrawal_fee_percent.valid() && 0 == *op.withdrawal_fee_percent ), + "Taker fee percent can only be updated if withdrawal fee percent is zero or " + "withdrawal fee percent is to be updated to zero at the same time" ); + } + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE + +void_result liquidity_pool_update_evaluator::do_apply(const liquidity_pool_update_operation& op) const +{ try { + database& d = db(); + + d.modify( *_pool, [&op](liquidity_pool_object& obj) { + if( op.taker_fee_percent.valid() ) + obj.taker_fee_percent = *op.taker_fee_percent; + if( op.withdrawal_fee_percent.valid() ) + obj.withdrawal_fee_percent = *op.withdrawal_fee_percent; + }); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE void_result liquidity_pool_deposit_evaluator::do_evaluate(const liquidity_pool_deposit_operation& op) { try { @@ -181,7 +219,7 @@ void_result liquidity_pool_deposit_evaluator::do_evaluate(const liquidity_pool_d } return void_result(); -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE generic_exchange_operation_result liquidity_pool_deposit_evaluator::do_apply( const liquidity_pool_deposit_operation& op) @@ -211,7 +249,7 @@ generic_exchange_operation_result liquidity_pool_deposit_evaluator::do_apply( result.received.emplace_back( _account_receives ); return result; -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE void_result liquidity_pool_withdraw_evaluator::do_evaluate(const liquidity_pool_withdraw_operation& op) { try { @@ -265,7 +303,7 @@ void_result liquidity_pool_withdraw_evaluator::do_evaluate(const liquidity_pool_ } return void_result(); -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE generic_exchange_operation_result liquidity_pool_withdraw_evaluator::do_apply( const liquidity_pool_withdraw_operation& op) @@ -300,7 +338,7 @@ generic_exchange_operation_result liquidity_pool_withdraw_evaluator::do_apply( result.fees.emplace_back( _fee_b ); return result; -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE void_result liquidity_pool_exchange_evaluator::do_evaluate(const liquidity_pool_exchange_operation& op) { try { @@ -398,7 +436,7 @@ void_result liquidity_pool_exchange_evaluator::do_evaluate(const liquidity_pool_ _pool_taker_fee = asset( static_cast( pool_taker_fee ), op.min_to_receive.asset_id ); return void_result(); -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE generic_exchange_operation_result liquidity_pool_exchange_evaluator::do_apply( const liquidity_pool_exchange_operation& op) @@ -445,6 +483,6 @@ generic_exchange_operation_result liquidity_pool_exchange_evaluator::do_apply( result.fees.emplace_back( _pool_taker_fee ); return result; -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE } } // graphene::chain diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 11aee840a5..ec9e5130b6 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -33,12 +33,23 @@ #include #include +#include namespace graphene { namespace chain { void_result limit_order_create_evaluator::do_evaluate(const limit_order_create_operation& op) { try { const database& d = db(); + if( op.extensions.value.on_fill.valid() ) + { + FC_ASSERT( HARDFORK_CORE_2535_PASSED( d.head_block_time() ) , + "The on_fill extension is not allowed until the core-2535 hardfork"); + FC_ASSERT( 1 == op.extensions.value.on_fill->size(), + "The on_fill action list must contain only one action until expanded in a future hardfork" ); + const auto& take_profit_action = op.extensions.value.on_fill->front().get(); + FC_ASSERT( d.find( take_profit_action.fee_asset_id ), "Fee asset does not exist" ); + } + FC_ASSERT( op.expiration >= d.head_block_time() ); _seller = this->fee_paying_account; @@ -74,13 +85,13 @@ void_result limit_order_create_evaluator::do_evaluate(const limit_order_create_o ("balance",d.get_balance(*_seller,*_sell_asset))("amount_to_sell",op.amount_to_sell) ); return void_result(); -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE void limit_order_create_evaluator::convert_fee() { if( db().head_block_time() <= HARDFORK_CORE_604_TIME ) generic_evaluator::convert_fee(); - else if( !trx_state->skip_fee && fee_asset->get_id() != asset_id_type() ) + else if( fee_asset->get_id() != asset_id_type() ) { db().modify(*fee_asset_dyn_data, [this](asset_dynamic_data_object& d) { d.fee_pool -= core_fee_paid; @@ -118,6 +129,8 @@ object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_o obj.expiration = op.expiration; obj.deferred_fee = _deferred_fee; obj.deferred_paid_fee = _deferred_paid_fee; + if( op.extensions.value.on_fill.valid() ) + obj.on_fill = *op.extensions.value.on_fill; }); object_id_type order_id = new_order_object.id; // save this because we may remove the object by filling it bool filled; @@ -132,7 +145,323 @@ object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_o ("op",op) ); return order_id; -} FC_CAPTURE_AND_RETHROW( (op) ) } +} FC_CAPTURE_AND_RETHROW( (op) ) } // GCOVR_EXCL_LINE + +void limit_order_update_evaluator::convert_fee() +{ + if( fee_asset->get_id() != asset_id_type() ) + { + db().modify(*fee_asset_dyn_data, [this](asset_dynamic_data_object& addo) { + addo.fee_pool -= core_fee_paid; + }); + } +} + +void limit_order_update_evaluator::pay_fee() +{ + _deferred_fee = core_fee_paid; + if( fee_asset->get_id() != asset_id_type() ) + _deferred_paid_fee = fee_from_account; +} + +void_result limit_order_update_evaluator::do_evaluate(const limit_order_update_operation& o) +{ try { + const database& d = db(); + FC_ASSERT( HARDFORK_CORE_1604_PASSED( d.head_block_time() ) , "Operation has not activated yet"); + + if( o.on_fill.valid() ) + { + // Note: Assuming that HF core-1604 and HF core-2535 will take place at the same time, + // no check for HF core-2535 here. + FC_ASSERT( o.on_fill->size() <= 1, + "The on_fill action list must contain zero or one action until expanded in a future hardfork" ); + if( !o.on_fill->empty() ) + { + const auto& take_profit_action = o.on_fill->front().get(); + FC_ASSERT( d.find( take_profit_action.fee_asset_id ), "Fee asset does not exist" ); + } + } + + _order = d.find( o.order ); + + GRAPHENE_ASSERT( _order != nullptr, + limit_order_update_nonexist_order, + "Limit order ${oid} does not exist, cannot update", + ("oid", o.order) ); + + // Check this is my order + GRAPHENE_ASSERT( o.seller == _order->seller, + limit_order_update_owner_mismatch, + "Limit order ${oid} is owned by someone else, cannot update", + ("oid", o.order) ); + + // Check new price is compatible and appropriate + if (o.new_price) { + auto base_id = o.new_price->base.asset_id; + auto quote_id = o.new_price->quote.asset_id; + FC_ASSERT(base_id == _order->sell_price.base.asset_id && quote_id == _order->sell_price.quote.asset_id, + "Cannot update limit order with incompatible price"); + + // Do not allow inappropriate price manipulation + auto max_amount_for_sale = std::max( _order->for_sale, _order->sell_price.base.amount ); + if( o.delta_amount_to_sell ) + max_amount_for_sale += o.delta_amount_to_sell->amount; + FC_ASSERT( o.new_price->base.amount <= max_amount_for_sale, + "The base amount in the new price cannot be greater than the estimated maximum amount for sale" ); + + } + + // Check delta asset is compatible + if (o.delta_amount_to_sell) { + const auto& delta = *o.delta_amount_to_sell; + FC_ASSERT(delta.asset_id == _order->sell_price.base.asset_id, + "Cannot update limit order with incompatible asset"); + if (delta.amount < 0) + FC_ASSERT(_order->for_sale > -delta.amount, + "Cannot deduct all or more from order than order contains"); + // Note: if the delta amount is positive, account balance will be checked when calling adjust_balance() + } + + // Check dust + if (o.new_price || (o.delta_amount_to_sell && o.delta_amount_to_sell->amount < 0)) { + auto new_price = o.new_price? *o.new_price : _order->sell_price; + auto new_amount = _order->amount_for_sale(); + if (o.delta_amount_to_sell) + new_amount += *o.delta_amount_to_sell; + auto new_amount_to_receive = new_amount * new_price; + + FC_ASSERT( new_amount_to_receive.amount > 0, + "Cannot update limit order: order becomes too small; cancel order instead" ); + } + + // Check expiration is in the future + if (o.new_expiration) + FC_ASSERT( *o.new_expiration >= d.head_block_time(), "Cannot update limit order to expire in the past" ); + + // Check asset authorization + // TODO refactor to fix duplicate code (see limit_order_create) + const auto sell_asset_id = _order->sell_asset_id(); + const auto receive_asset_id = _order->receive_asset_id(); + const auto& sell_asset = sell_asset_id(d); + const auto& receive_asset = receive_asset_id(d); + const auto& seller = *this->fee_paying_account; + + if( !sell_asset.options.whitelist_markets.empty() ) + { + FC_ASSERT( sell_asset.options.whitelist_markets.find( receive_asset_id ) + != sell_asset.options.whitelist_markets.end(), + "This market has not been whitelisted by the selling asset" ); + } + if( !sell_asset.options.blacklist_markets.empty() ) + { + FC_ASSERT( sell_asset.options.blacklist_markets.find( receive_asset_id ) + == sell_asset.options.blacklist_markets.end(), + "This market has been blacklisted by the selling asset" ); + } + + FC_ASSERT( is_authorized_asset( d, seller, sell_asset ), + "The account is not allowed to transact the selling asset" ); + + FC_ASSERT( is_authorized_asset( d, seller, receive_asset ), + "The account is not allowed to transact the receiving asset" ); + + return {}; +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE + +void limit_order_update_evaluator::process_deferred_fee() +{ + // Attempt to deduct a possibly discounted order cancellation fee, and refund the remainder. + // Only deduct fee if there is any fee deferred. + // TODO fix duplicate code (see database::cancel_limit_order()) + if( _order->deferred_fee <= 0 ) + return; + + database& d = db(); + + share_type deferred_fee = _order->deferred_fee; + asset deferred_paid_fee = _order->deferred_paid_fee; + const asset_dynamic_data_object* deferred_fee_asset_dyn_data = nullptr; + const auto& current_fees = d.current_fee_schedule(); + asset core_cancel_fee = current_fees.calculate_fee( limit_order_cancel_operation() ); + if( core_cancel_fee.amount > 0 ) + { + // maybe-discounted cancel_fee calculation: + // limit_order_cancel_fee * limit_order_update_fee / limit_order_create_fee + asset core_create_fee = current_fees.calculate_fee( limit_order_create_operation() ); + fc::uint128_t fee128( core_cancel_fee.amount.value ); + if( core_create_fee.amount > 0 ) + { + asset core_update_fee = current_fees.calculate_fee( limit_order_update_operation() ); + fee128 *= core_update_fee.amount.value; + fee128 /= core_create_fee.amount.value; + } + // cap the fee + if( fee128 > static_cast( deferred_fee.value ) ) + fee128 = deferred_fee.value; + core_cancel_fee.amount = static_cast( fee128 ); + } + + // if there is any CORE fee to deduct, redirect it to referral program + if( core_cancel_fee.amount > 0 ) + { + if( !_seller_acc_stats ) + _seller_acc_stats = &d.get_account_stats_by_owner( _order->seller ); + d.modify( *_seller_acc_stats, [&core_cancel_fee, &d]( account_statistics_object& obj ) { + obj.pay_fee( core_cancel_fee.amount, d.get_global_properties().parameters.cashback_vesting_threshold ); + } ); + deferred_fee -= core_cancel_fee.amount; + // handle originally paid fee if any: + // to_deduct = round_up( paid_fee * core_cancel_fee / deferred_core_fee_before_deduct ) + if( deferred_paid_fee.amount > 0 ) + { + fc::uint128_t fee128( deferred_paid_fee.amount.value ); + fee128 *= core_cancel_fee.amount.value; + // to round up + fee128 += _order->deferred_fee.value; + fee128 -= 1; + fee128 /= _order->deferred_fee.value; + share_type cancel_fee_amount = static_cast(fee128); + // cancel_fee should be positive, pay it to asset's accumulated_fees + deferred_fee_asset_dyn_data = &deferred_paid_fee.asset_id(d).dynamic_asset_data_id(d); + d.modify( *deferred_fee_asset_dyn_data, [&cancel_fee_amount](asset_dynamic_data_object& addo) { + addo.accumulated_fees += cancel_fee_amount; + }); + // cancel_fee should be no more than deferred_paid_fee + deferred_paid_fee.amount -= cancel_fee_amount; + } + } + + // refund fee + if( 0 == _order->deferred_paid_fee.amount ) + { + // be here, order.create_time <= HARDFORK_CORE_604_TIME, or fee paid in CORE, or no fee to refund. + // if order was created before hard fork 604, + // see it as fee paid in CORE, deferred_fee should be refunded to order owner but not fee pool + d.adjust_balance( _order->seller, deferred_fee ); + } + else // need to refund fee in originally paid asset + { + d.adjust_balance( _order->seller, deferred_paid_fee ); + // be here, must have: fee_asset != CORE + if( !deferred_fee_asset_dyn_data ) + deferred_fee_asset_dyn_data = &deferred_paid_fee.asset_id(d).dynamic_asset_data_id(d); + d.modify( *deferred_fee_asset_dyn_data, [&deferred_fee](asset_dynamic_data_object& addo) { + addo.fee_pool += deferred_fee; + }); + } + +} + +bool limit_order_update_evaluator::is_linked_tp_order_compatible(const limit_order_update_operation& o) const +{ + if( !o.on_fill ) // there is no change to on_fill, so do nothing + return true; + bool is_compatible = true; + if( o.on_fill->empty() ) + { + if( !_order->on_fill.empty() ) // This order's on_fill is being removed + { + // Two scenarios: + // 1. The linked order is generated by this order, now this order's on_fill is being removed, so unlink + // 2. This order is generated by the linked order, and "repeat" was true, now this order's on_fill is + // being removed, so it becomes incompatible with the linked order, so unlink + is_compatible = false; + } + // else there is no change, nothing to do here + } + else // o.on_fill is not empty + { + if( _order->on_fill.empty() ) + { + // It means this order was generated by the linked order, and the linked order's "repeat" is false. + // We are adding on_fill to this order, so it becomes incompatible with the linked order, so unlink. + is_compatible = false; + } + else // Not empty + { + // Two scenarios: + // 1. Both order's "repeat" are true + // 2. This order's "repeat" was false, and the linked order was generated by this order + // + // Either way, if "spread_percent" or "repeat" in on_fill changed, unlink the linked take profit order. + const auto& old_take_profit_action = _order->get_take_profit_action(); + const auto& new_take_profit_action = o.on_fill->front().get(); + if( old_take_profit_action.spread_percent != new_take_profit_action.spread_percent + || old_take_profit_action.repeat != new_take_profit_action.repeat ) + { + is_compatible = false; + } + } // whether order's on_fill is empty (both handled) + } // whether o.on_fill is empty (both handled) + return is_compatible; +}; + +void_result limit_order_update_evaluator::do_apply(const limit_order_update_operation& o) +{ try { + database& d = db(); + + // Adjust account balance + if( o.delta_amount_to_sell ) + { + d.adjust_balance( o.seller, -*o.delta_amount_to_sell ); + if( o.delta_amount_to_sell->asset_id == asset_id_type() ) + { + _seller_acc_stats = &d.get_account_stats_by_owner( o.seller ); + d.modify( *_seller_acc_stats, [&o]( account_statistics_object& bal ) { + bal.total_core_in_orders += o.delta_amount_to_sell->amount; + }); + } + } + + // Process deferred fee in the order. + process_deferred_fee(); + + // Process linked take profit order + bool unlink = false; + if( _order->take_profit_order_id.valid() ) + { + // If price changed, unless it is triggered by on_fill of the linked take profit order, + // unlink the linked take profit order. + if( !trx_state->skip_limit_order_price_check + && o.new_price.valid() && *o.new_price != _order->sell_price ) + { + unlink = true; + } + // If on_fill changed and the order became incompatible with the linked order, unlink + else + unlink = !is_linked_tp_order_compatible( o ); + + // Now update database + if( unlink ) + { + const auto& take_profit_order = (*_order->take_profit_order_id)(d); + d.modify( take_profit_order, []( limit_order_object& loo ) { + loo.take_profit_order_id.reset(); + }); + } + } + + // Update order + d.modify(*_order, [&o,this,unlink](limit_order_object& loo) { + if (o.new_price) + loo.sell_price = *o.new_price; + if (o.delta_amount_to_sell) + loo.for_sale += o.delta_amount_to_sell->amount; + if (o.new_expiration) + loo.expiration = *o.new_expiration; + loo.deferred_fee = _deferred_fee; + loo.deferred_paid_fee = _deferred_paid_fee; + if( o.on_fill ) + loo.on_fill= *o.on_fill; + if( unlink ) + loo.take_profit_order_id.reset(); + }); + + // Perform order matching if necessary + d.apply_order(*_order); + + return {}; +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE void_result limit_order_cancel_evaluator::do_evaluate(const limit_order_cancel_operation& o) { try { @@ -151,7 +480,7 @@ void_result limit_order_cancel_evaluator::do_evaluate(const limit_order_cancel_o ("oid", o.order) ); return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE asset limit_order_cancel_evaluator::do_apply(const limit_order_cancel_operation& o) const { try { @@ -173,7 +502,7 @@ asset limit_order_cancel_evaluator::do_apply(const limit_order_cancel_operation& } return refunded; -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE void_result call_order_update_evaluator::do_evaluate(const call_order_update_operation& o) { try { @@ -206,7 +535,7 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope /// if there is a settlement for this asset, then no further margin positions may be taken and /// all existing margin positions should have been closed va database::globally_settle_asset - FC_ASSERT( !_bitasset_data->has_settlement(), + FC_ASSERT( !_bitasset_data->is_globally_settled(), "Cannot update debt position when the asset has been globally settled" ); FC_ASSERT( o.delta_collateral.asset_id == _bitasset_data->options.short_backing_asset, @@ -257,7 +586,7 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope // which is now removed since the check is implicitly done later by `adjust_balance()` in `do_apply()`. return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE object_id_type call_order_update_evaluator::do_apply(const call_order_update_operation& o) @@ -465,7 +794,7 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope } return call_order_id; -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE void_result bid_collateral_evaluator::do_evaluate(const bid_collateral_operation& o) { try { @@ -487,7 +816,7 @@ void_result bid_collateral_evaluator::do_evaluate(const bid_collateral_operation _bitasset_data = &_debt_asset->bitasset_data(d); - FC_ASSERT( _bitasset_data->has_settlement(), "Cannot bid since the asset is not globally settled" ); + FC_ASSERT( _bitasset_data->is_globally_settled(), "Cannot bid since the asset is not globally settled" ); FC_ASSERT( o.additional_collateral.asset_id == _bitasset_data->options.short_backing_asset ); @@ -527,7 +856,7 @@ void_result bid_collateral_evaluator::do_evaluate(const bid_collateral_operation } return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE void_result bid_collateral_evaluator::do_apply(const bid_collateral_operation& o) const @@ -549,6 +878,6 @@ void_result bid_collateral_evaluator::do_apply(const bid_collateral_operation& o // Note: CORE asset in collateral_bid_object is not counted in account_stats.total_core_in_orders return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE } } // graphene::chain diff --git a/libraries/chain/market_object.cpp b/libraries/chain/market_object.cpp index 42ec31d5d0..9f9fc53572 100644 --- a/libraries/chain/market_object.cpp +++ b/libraries/chain/market_object.cpp @@ -308,8 +308,8 @@ share_type call_order_object::get_max_debt_to_cover( price match_price, FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::limit_order_object, (graphene::db::object), - (expiration)(seller)(for_sale)(sell_price)(deferred_fee)(deferred_paid_fee) - (is_settled_debt) + (expiration)(seller)(for_sale)(sell_price)(filled_amount)(deferred_fee)(deferred_paid_fee) + (is_settled_debt)(on_fill)(take_profit_order_id) ) FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::call_order_object, (graphene::db::object), diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 0e16e1d904..62efcb708e 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -69,6 +69,20 @@ struct proposal_operation_hardfork_visitor template void operator()(const T &v) const {} + // TODO review and cleanup code below after hard fork + // hf_1604 + void operator()(const graphene::chain::limit_order_update_operation &) const { + FC_ASSERT( HARDFORK_CORE_1604_PASSED(block_time), "Operation is not enabled yet" ); + } + + // hf_2535 + void operator()(const graphene::chain::limit_order_create_operation& op) const { + if( !HARDFORK_CORE_2535_PASSED(block_time) ) { + FC_ASSERT( !op.extensions.value.on_fill.valid(), + "The on_fill extension is not allowed until the core-2535 hardfork"); + } + } + void operator()(const graphene::chain::asset_create_operation &v) const { detail::check_asset_options_hf_1774(block_time, v.common_options); detail::check_asset_options_hf_bsip_48_75(block_time, v.common_options); @@ -143,6 +157,10 @@ struct proposal_operation_hardfork_visitor FC_ASSERT(!op.new_parameters.current_fees->exists()); FC_ASSERT(!op.new_parameters.current_fees->exists()); } + if (!HARDFORK_CORE_1604_PASSED(block_time)) { + FC_ASSERT(!op.new_parameters.current_fees->exists(), + "Cannot set fees for limit_order_update_operation before its hardfork time"); + } if (!HARDFORK_BSIP_40_PASSED(block_time)) { FC_ASSERT(!op.new_parameters.extensions.value.custom_authority_options.valid(), "Unable to set Custom Authority Options before hardfork BSIP 40"); @@ -205,6 +223,14 @@ struct proposal_operation_hardfork_visitor FC_ASSERT(!op.new_parameters.current_fees->exists(), "Unable to define fees for credit offer operations prior to the core-2362 hardfork"); } + if (!HARDFORK_CORE_2595_PASSED(block_time)) { + FC_ASSERT(!op.new_parameters.current_fees->exists(), + "Unable to define fees for credit deal update operation prior to the core-2595 hardfork"); + } + if (!HARDFORK_CORE_2604_PASSED(block_time)) { + FC_ASSERT(!op.new_parameters.current_fees->exists(), + "Unable to define fees for liquidity pool update operation prior to the core-2604 hardfork"); + } } void operator()(const graphene::chain::htlc_create_operation &op) const { FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); @@ -246,6 +272,9 @@ struct proposal_operation_hardfork_visitor void operator()(const graphene::chain::liquidity_pool_delete_operation&) const { FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), "Not allowed until the LP hardfork" ); } + void operator()(const graphene::chain::liquidity_pool_update_operation&) const { + FC_ASSERT( HARDFORK_CORE_2604_PASSED(block_time), "Not allowed until the core-2604 hardfork" ); + } void operator()(const graphene::chain::liquidity_pool_deposit_operation&) const { FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), "Not allowed until the LP hardfork" ); } @@ -279,13 +308,20 @@ struct proposal_operation_hardfork_visitor void operator()(const graphene::chain::credit_offer_update_operation&) const { FC_ASSERT( HARDFORK_CORE_2362_PASSED(block_time), "Not allowed until the core-2362 hardfork" ); } - void operator()(const graphene::chain::credit_offer_accept_operation&) const { + void operator()(const graphene::chain::credit_offer_accept_operation& op) const { FC_ASSERT( HARDFORK_CORE_2362_PASSED(block_time), "Not allowed until the core-2362 hardfork" ); + if( !HARDFORK_CORE_2595_PASSED(block_time) ) { + FC_ASSERT( !op.extensions.value.auto_repay.valid(), + "auto_repay unavailable until the core-2595 hardfork"); + } } void operator()(const graphene::chain::credit_deal_repay_operation&) const { FC_ASSERT( HARDFORK_CORE_2362_PASSED(block_time), "Not allowed until the core-2362 hardfork" ); } // Note: credit_deal_expired_operation is a virtual operation thus no need to add code here + void operator()(const graphene::chain::credit_deal_update_operation&) const { + FC_ASSERT( HARDFORK_CORE_2595_PASSED(block_time), "Not allowed until the core-2595 hardfork" ); + } // loop and self visit in proposals void operator()(const graphene::chain::proposal_create_operation &v) const { @@ -403,7 +439,7 @@ void_result proposal_create_evaluator::do_evaluate( const proposal_create_operat _proposed_trx.validate(); return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE object_id_type proposal_create_evaluator::do_apply( const proposal_create_operation& o ) { try { @@ -438,7 +474,7 @@ object_id_type proposal_create_evaluator::do_apply( const proposal_create_operat }); return proposal.id; -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE void_result proposal_update_evaluator::do_evaluate( const proposal_update_operation& o ) { try { @@ -462,7 +498,7 @@ void_result proposal_update_evaluator::do_evaluate( const proposal_update_operat } return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE void_result proposal_update_evaluator::do_apply(const proposal_update_operation& o) { try { @@ -499,14 +535,15 @@ void_result proposal_update_evaluator::do_apply(const proposal_update_operation& d.modify(*_proposal, [&e](proposal_object& p) { p.fail_reason = e.to_string(fc::log_level(fc::log_level::all)); }); - wlog("Proposed transaction ${id} failed to apply once approved with exception:\n----\n${reason}\n----\nWill try again when it expires.", + wlog("Proposed transaction ${id} failed to apply once approved with exception:\n" + "----\n${reason}\n----\nWill try again when it expires.", ("id", o.proposal)("reason", e.to_detail_string())); _proposal_failed = true; } } return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE void_result proposal_delete_evaluator::do_evaluate(const proposal_delete_operation& o) { try { @@ -521,14 +558,14 @@ void_result proposal_delete_evaluator::do_evaluate(const proposal_delete_operati ("provided", o.fee_paying_account)("required", *required_approvals)); return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE void_result proposal_delete_evaluator::do_apply(const proposal_delete_operation& o) { try { db().remove(*_proposal); return void_result(); -} FC_CAPTURE_AND_RETHROW( (o) ) } +} FC_CAPTURE_AND_RETHROW( (o) ) } // GCOVR_EXCL_LINE } } // graphene::chain diff --git a/libraries/chain/small_objects.cpp b/libraries/chain/small_objects.cpp index 291eac1230..c914850b96 100644 --- a/libraries/chain/small_objects.cpp +++ b/libraries/chain/small_objects.cpp @@ -234,6 +234,7 @@ FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::credit_deal_object, (graphene:: (collateral_amount) (fee_rate) (latest_repay_time) + (auto_repay) ) FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::credit_deal_summary_object, (graphene::db::object), diff --git a/libraries/fc b/libraries/fc index 4d024a83b7..33cd59ca49 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 4d024a83b774da0e186c0c6c070e695f563da373 +Subproject commit 33cd59ca4950542ee048b0be9f9f52ce26a863b3 diff --git a/libraries/net/node.cpp b/libraries/net/node.cpp index f69bb98cd2..eebc21a74a 100644 --- a/libraries/net/node.cpp +++ b/libraries/net/node.cpp @@ -451,7 +451,7 @@ namespace graphene { namespace net { namespace detail { ilog( "p2p_network_connect_loop canceled" ); throw; } - FC_CAPTURE_AND_LOG( (0) ) + FC_CAPTURE_AND_LOG( (0) ) // GCOVR_EXCL_LINE }// while !canceled } @@ -482,7 +482,7 @@ namespace graphene { namespace net { namespace detail { ilog( "update_seed_nodes_task canceled" ); throw; } - FC_CAPTURE_AND_LOG( (_seed_nodes) ) + FC_CAPTURE_AND_LOG( (_seed_nodes) ) // GCOVR_EXCL_LINE schedule_next_update_seed_nodes_task(); } @@ -3630,6 +3630,8 @@ namespace graphene { namespace net { namespace detail { case graphene::chain::limit_order_create_selling_asset_unauthorized::code_enum::code_value : case graphene::chain::limit_order_create_receiving_asset_unauthorized::code_enum::code_value : case graphene::chain::limit_order_create_insufficient_balance::code_enum::code_value : + case graphene::chain::limit_order_update_nonexist_order::code_enum::code_value : + case graphene::chain::limit_order_update_owner_mismatch::code_enum::code_value : case graphene::chain::limit_order_cancel_nonexist_order::code_enum::code_value : case graphene::chain::limit_order_cancel_owner_mismatch::code_enum::code_value : case graphene::chain::liquidity_pool_exchange_unfillable_price::code_enum::code_value : @@ -4045,7 +4047,7 @@ namespace graphene { namespace net { namespace detail { // limit the rate at which we accept connections to mitigate DOS attacks fc::usleep( fc::milliseconds(10) ); - } FC_CAPTURE_AND_LOG( (0) ) + } FC_CAPTURE_AND_LOG( (0) ) // GCOVR_EXCL_LINE } } // accept_loop() @@ -4474,7 +4476,7 @@ namespace graphene { namespace net { namespace detail { FC_THROW("Bad port: ${port}", ("port", port_string)); } } - FC_CAPTURE_AND_RETHROW((in)) + FC_CAPTURE_AND_RETHROW((in)) // GCOVR_EXCL_LINE } void node_impl::resolve_seed_node_and_add(const std::string& endpoint_string) diff --git a/libraries/plugins/api_helper_indexes/api_helper_indexes.cpp b/libraries/plugins/api_helper_indexes/api_helper_indexes.cpp index e6cd8100f6..32874b1fb2 100644 --- a/libraries/plugins/api_helper_indexes/api_helper_indexes.cpp +++ b/libraries/plugins/api_helper_indexes/api_helper_indexes.cpp @@ -50,7 +50,7 @@ void amount_in_collateral_index::object_inserted( const object& objct ) itr->second += o.collateral; } -} FC_CAPTURE_AND_RETHROW( (objct) ) } +} FC_CAPTURE_AND_RETHROW( (objct) ) } // GCOVR_EXCL_LINE void amount_in_collateral_index::object_removed( const object& objct ) { try { @@ -68,31 +68,31 @@ void amount_in_collateral_index::object_removed( const object& objct ) itr->second -= o.collateral; } -} FC_CAPTURE_AND_RETHROW( (objct) ) } +} FC_CAPTURE_AND_RETHROW( (objct) ) } // GCOVR_EXCL_LINE void amount_in_collateral_index::about_to_modify( const object& objct ) { try { object_removed( objct ); -} FC_CAPTURE_AND_RETHROW( (objct) ) } +} FC_CAPTURE_AND_RETHROW( (objct) ) } // GCOVR_EXCL_LINE void amount_in_collateral_index::object_modified( const object& objct ) { try { object_inserted( objct ); -} FC_CAPTURE_AND_RETHROW( (objct) ) } +} FC_CAPTURE_AND_RETHROW( (objct) ) } // GCOVR_EXCL_LINE share_type amount_in_collateral_index::get_amount_in_collateral( const asset_id_type& asst )const { try { auto itr = in_collateral.find( asst ); if( itr == in_collateral.end() ) return 0; return itr->second; -} FC_CAPTURE_AND_RETHROW( (asst) ) } +} FC_CAPTURE_AND_RETHROW( (asst) ) } // GCOVR_EXCL_LINE share_type amount_in_collateral_index::get_backing_collateral( const asset_id_type& asst )const { try { auto itr = backing_collateral.find( asst ); if( itr == backing_collateral.end() ) return 0; return itr->second; -} FC_CAPTURE_AND_RETHROW( (asst) ) } +} FC_CAPTURE_AND_RETHROW( (asst) ) } // GCOVR_EXCL_LINE void asset_in_liquidity_pools_index::object_inserted( const object& objct ) { try { @@ -100,7 +100,7 @@ void asset_in_liquidity_pools_index::object_inserted( const object& objct ) const liquidity_pool_id_type pool_id = o.get_id(); asset_in_pools_map[ o.asset_a ].insert( pool_id ); // Note: [] operator will create an entry if not found asset_in_pools_map[ o.asset_b ].insert( pool_id ); -} FC_CAPTURE_AND_RETHROW( (objct) ) } +} FC_CAPTURE_AND_RETHROW( (objct) ) } // GCOVR_EXCL_LINE void asset_in_liquidity_pools_index::object_removed( const object& objct ) { try { @@ -109,7 +109,7 @@ void asset_in_liquidity_pools_index::object_removed( const object& objct ) asset_in_pools_map[ o.asset_a ].erase( pool_id ); asset_in_pools_map[ o.asset_b ].erase( pool_id ); // Note: do not erase entries with an empty set from the map in order to avoid read/write race conditions -} FC_CAPTURE_AND_RETHROW( (objct) ) } +} FC_CAPTURE_AND_RETHROW( (objct) ) } // GCOVR_EXCL_LINE void asset_in_liquidity_pools_index::about_to_modify( const object& objct ) { @@ -250,6 +250,6 @@ void api_helper_indexes::refresh_next_ids() object_id_type next_object_ids_index::get_next_id( uint8_t space_id, uint8_t type_id ) const { try { return _next_ids.at( std::make_pair( space_id, type_id ) ); -} FC_CAPTURE_AND_RETHROW( (space_id)(type_id) ) } +} FC_CAPTURE_AND_RETHROW( (space_id)(type_id) ) } // GCOVR_EXCL_LINE } } diff --git a/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp b/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp index 62e4a10312..ef0e46361f 100644 --- a/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp +++ b/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp @@ -272,6 +272,7 @@ void elasticsearch_plugin_impl::doOperationHistory( const optional trx_in_block; os.op_in_trx = oho->op_in_trx; os.virtual_op = oho->virtual_op; + os.is_virtual = oho->is_virtual; os.fee_payer = oho->op.visit( get_fee_payer_visitor() ); if(_options.operation_string) @@ -291,7 +292,7 @@ void elasticsearch_plugin_impl::doOperationHistory( const optional es->query( my->_options.index_prefix + "*/_doc/_search", query ); + const auto uri = my->_options.index_prefix + ( my->is_es_version_7_or_above ? "*/_search" : "*/_doc/_search" ); + const auto response = my->es->query( uri, query ); variant variant_response = fc::json::from_string(response); const auto source = variant_response["hits"]["hits"][size_t(0)]["_source"]; return fromEStoOperation(source); @@ -677,7 +682,8 @@ vector elasticsearch_plugin::get_account_history( if( !my->es->check_status() ) return result; - const auto response = my->es->query( my->_options.index_prefix + "*/_doc/_search", query ); + const auto uri = my->_options.index_prefix + ( my->is_es_version_7_or_above ? "*/_search" : "*/_doc/_search" ); + const auto response = my->es->query( uri, query ); variant variant_response = fc::json::from_string(response); diff --git a/libraries/plugins/elasticsearch/include/graphene/elasticsearch/elasticsearch_plugin.hpp b/libraries/plugins/elasticsearch/include/graphene/elasticsearch/elasticsearch_plugin.hpp index 797fe3b8c0..e2ad5bf483 100644 --- a/libraries/plugins/elasticsearch/include/graphene/elasticsearch/elasticsearch_plugin.hpp +++ b/libraries/plugins/elasticsearch/include/graphene/elasticsearch/elasticsearch_plugin.hpp @@ -83,6 +83,7 @@ struct operation_history_struct { uint16_t trx_in_block; uint16_t op_in_trx; uint32_t virtual_op; + bool is_virtual; account_id_type fee_payer; std::string op; std::string operation_result; @@ -147,7 +148,7 @@ struct bulk_struct { FC_REFLECT_ENUM( graphene::elasticsearch::mode, (only_save)(only_query)(all) ) FC_REFLECT( graphene::elasticsearch::operation_history_struct, - (trx_in_block)(op_in_trx)(virtual_op)(fee_payer) + (trx_in_block)(op_in_trx)(virtual_op)(is_virtual)(fee_payer) (op)(operation_result)(op_object)(operation_result_object) ) FC_REFLECT( graphene::elasticsearch::block_struct, (block_num)(block_time)(trx_id) ) FC_REFLECT( graphene::elasticsearch::fee_struct, (asset)(asset_name)(amount)(amount_units) ) diff --git a/libraries/plugins/es_objects/es_objects.cpp b/libraries/plugins/es_objects/es_objects.cpp index 307938feb6..bc7d768541 100644 --- a/libraries/plugins/es_objects/es_objects.cpp +++ b/libraries/plugins/es_objects/es_objects.cpp @@ -358,12 +358,12 @@ void es_objects_plugin_impl::send_bulk_if_ready( bool force ) // send data to elasticsearch when being forced or bulk is too large if( !es->send_bulk( bulk_lines ) ) { - elog( "Error sending ${n} lines of bulk data to ElasticSearch, the first lines are:", - ("n",bulk_lines.size()) ); + elog( "Error sending ${n} lines of bulk data to ElasticSearch, the first lines are:", // GCOVR_EXCL_LINE + ("n",bulk_lines.size()) ); // GCOVR_EXCL_LINE const auto log_max = std::min( bulk_lines.size(), size_t(10) ); for( size_t i = 0; i < log_max; ++i ) { - edump( (bulk_lines[i]) ); + edump( (bulk_lines[i]) ); // GCOVR_EXCL_LINE } FC_THROW_EXCEPTION( graphene::chain::plugin_exception, "Error populating ES database, we are going to keep trying." ); diff --git a/libraries/plugins/market_history/market_history_plugin.cpp b/libraries/plugins/market_history/market_history_plugin.cpp index 382ed4fc62..b4dfa9dba9 100644 --- a/libraries/plugins/market_history/market_history_plugin.cpp +++ b/libraries/plugins/market_history/market_history_plugin.cpp @@ -511,6 +511,11 @@ struct get_liquidity_pool_id_visitor return o.pool; } + result_type operator()( const liquidity_pool_update_operation& o )const + { + return o.pool; + } + result_type operator()( const liquidity_pool_deposit_operation& o )const { return o.pool; @@ -744,12 +749,14 @@ void market_history_plugin::plugin_set_program_options( ) { cli.add_options() - ("bucket-size", boost::program_options::value()->default_value("[60,300,900,1800,3600,14400,86400]"), + ("bucket-size", + // 1m, 5m, 15m, 1h, 4h, 1d, 1w + boost::program_options::value()->default_value("[60,300,900,3600,14400,86400,604800]"), "Track market history by grouping orders into buckets of equal size measured " "in seconds specified as a JSON array of numbers") - ("history-per-size", boost::program_options::value()->default_value(1000), + ("history-per-size", boost::program_options::value()->default_value(1500), "How far back in time to track history for each bucket size, " - "measured in the number of buckets (default: 1000)") + "measured in the number of buckets (default: 1500)") ("max-order-his-records-per-market", boost::program_options::value()->default_value(1000), "Will only store this amount of matched orders for each market in order history for querying, " "or those meet the other option, which has more data (default: 1000). " diff --git a/libraries/protocol/account.cpp b/libraries/protocol/account.cpp index 9b77d8eca6..f290066d38 100644 --- a/libraries/protocol/account.cpp +++ b/libraries/protocol/account.cpp @@ -170,7 +170,7 @@ void account_options::validate() const "May not specify fewer witnesses or committee members than the number voted for."); } -share_type account_create_operation::calculate_fee( const fee_parameters_type& k )const +share_type account_create_operation::calculate_fee( const fee_params_t& k )const { auto core_fee_required = k.basic_fee; @@ -215,7 +215,7 @@ void account_create_operation::validate()const } } -share_type account_update_operation::calculate_fee( const fee_parameters_type& k )const +share_type account_update_operation::calculate_fee( const fee_params_t& k )const { auto core_fee_required = k.fee; if( new_options ) @@ -260,7 +260,7 @@ void account_update_operation::validate()const validate_special_authority( *extensions.value.active_special_authority ); } -share_type account_upgrade_operation::calculate_fee(const fee_parameters_type& k) const +share_type account_upgrade_operation::calculate_fee(const fee_params_t& k) const { if( upgrade_to_lifetime_member ) return k.membership_lifetime_fee; @@ -280,11 +280,11 @@ void account_transfer_operation::validate()const } } // graphene::protocol GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_options ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_create_operation::fee_parameters_type ) -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_transfer_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_create_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_whitelist_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_update_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_upgrade_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_transfer_operation::fee_params_t ) 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 ) diff --git a/libraries/protocol/assert.cpp b/libraries/protocol/assert.cpp index 2199b314d8..7acd7661c6 100644 --- a/libraries/protocol/assert.cpp +++ b/libraries/protocol/assert.cpp @@ -61,12 +61,12 @@ void assert_operation::validate()const * The fee for assert operations is proportional to their size, * but cheaper than a data fee because they require no storage */ -share_type assert_operation::calculate_fee(const fee_parameters_type& k)const +share_type assert_operation::calculate_fee(const fee_params_t& k)const { return k.fee * predicates.size(); } } } // namespace graphene::protocol -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::assert_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::assert_operation::fee_params_t ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::assert_operation ) diff --git a/libraries/protocol/asset_ops.cpp b/libraries/protocol/asset_ops.cpp index 09a7bab33c..38dd1bd307 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -74,12 +74,12 @@ bool is_valid_symbol( const string& symbol ) return true; } -share_type asset_issue_operation::calculate_fee(const fee_parameters_type& k)const +share_type asset_issue_operation::calculate_fee(const fee_params_t& k)const { return k.fee + calculate_data_fee( fc::raw::pack_size(memo), k.price_per_kbyte ); } -share_type asset_create_operation::calculate_fee( const asset_create_operation::fee_parameters_type& param, +share_type asset_create_operation::calculate_fee( const asset_create_operation::fee_params_t& param, const optional& sub_asset_creation_fee )const { share_type core_fee_required = param.long_symbol; @@ -157,7 +157,7 @@ void asset_update_issuer_operation::validate()const FC_ASSERT( issuer != new_issuer ); } -share_type asset_update_operation::calculate_fee(const asset_update_operation::fee_parameters_type& k)const +share_type asset_update_operation::calculate_fee(const asset_update_operation::fee_params_t& k)const { return k.fee + calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte ); } @@ -363,20 +363,20 @@ GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::additional_asset_ GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_operation::ext ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_publish_feed_operation::ext ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_create_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_global_settle_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_settle_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_fund_fee_pool_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_pool_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_create_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_global_settle_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_settle_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_fund_fee_pool_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_pool_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation::fee_params_t ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation::additional_options_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_issuer_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_bitasset_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_feed_producers_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_publish_feed_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_issue_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_reserve_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_issuer_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_bitasset_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_feed_producers_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_publish_feed_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_issue_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_reserve_operation::fee_params_t ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_create_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_global_settle_operation ) diff --git a/libraries/protocol/committee_member.cpp b/libraries/protocol/committee_member.cpp index d48372c944..fb67743b0b 100644 --- a/libraries/protocol/committee_member.cpp +++ b/libraries/protocol/committee_member.cpp @@ -49,9 +49,11 @@ void committee_member_update_global_parameters_operation::validate() const } } // graphene::protocol -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_create_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_update_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_update_global_parameters_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_create_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_update_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( + graphene::protocol::committee_member_update_global_parameters_operation::fee_params_t ) + GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_create_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_update_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_update_global_parameters_operation ) diff --git a/libraries/protocol/confidential.cpp b/libraries/protocol/confidential.cpp index 43a8b0587e..f05c85f450 100644 --- a/libraries/protocol/confidential.cpp +++ b/libraries/protocol/confidential.cpp @@ -61,7 +61,7 @@ void transfer_to_blind_operation::validate()const } } -share_type transfer_to_blind_operation::calculate_fee( const fee_parameters_type& k )const +share_type transfer_to_blind_operation::calculate_fee( const fee_params_t& k )const { return k.fee + outputs.size() * k.price_per_output; } @@ -134,7 +134,7 @@ void blind_transfer_operation::validate()const FC_ASSERT( fc::ecc::verify_sum( in, out, net_public ), "", ("net_public", net_public) ); } FC_CAPTURE_AND_RETHROW( (*this) ) } -share_type blind_transfer_operation::calculate_fee( const fee_parameters_type& k )const +share_type blind_transfer_operation::calculate_fee( const fee_params_t& k )const { return k.fee + outputs.size() * k.price_per_output; } @@ -156,9 +156,9 @@ stealth_confirmation::stealth_confirmation( const std::string& base58 ) } } // graphene::protocol -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_to_blind_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_from_blind_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::blind_transfer_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_to_blind_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_from_blind_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::blind_transfer_operation::fee_params_t ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_to_blind_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_from_blind_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::blind_transfer_operation ) diff --git a/libraries/protocol/credit_offer.cpp b/libraries/protocol/credit_offer.cpp index b8131b3a5f..27943bff63 100644 --- a/libraries/protocol/credit_offer.cpp +++ b/libraries/protocol/credit_offer.cpp @@ -76,7 +76,7 @@ void credit_offer_create_operation::validate()const validate_acceptable_borrowers( acceptable_borrowers ); } -share_type credit_offer_create_operation::calculate_fee( const fee_parameters_type& schedule )const +share_type credit_offer_create_operation::calculate_fee( const fee_params_t& schedule )const { share_type core_fee_required = schedule.fee; core_fee_required += calculate_data_fee( fc::raw::pack_size(*this), schedule.price_per_kbyte ); @@ -134,7 +134,7 @@ void credit_offer_update_operation::validate()const "Should change something - at least one of the optional data fields should be present" ); } -share_type credit_offer_update_operation::calculate_fee( const fee_parameters_type& schedule )const +share_type credit_offer_update_operation::calculate_fee( const fee_params_t& schedule )const { share_type core_fee_required = schedule.fee; core_fee_required += calculate_data_fee( fc::raw::pack_size(*this), schedule.price_per_kbyte ); @@ -146,6 +146,12 @@ void credit_offer_accept_operation::validate()const FC_ASSERT( fee.amount >= 0, "Fee should not be negative" ); FC_ASSERT( borrow_amount.amount > 0, "Amount to borrow should be positive" ); FC_ASSERT( collateral.amount > 0, "Collateral amount should be positive" ); + if( extensions.value.auto_repay.valid() ) + { + constexpr auto cdar_count = static_cast( credit_deal_auto_repayment_type::CDAR_TYPE_COUNT ); + FC_ASSERT( *extensions.value.auto_repay < cdar_count, + "auto_repay should be less than ${c}", ("c",cdar_count) ); + } } void credit_deal_repay_operation::validate()const @@ -157,13 +163,25 @@ void credit_deal_repay_operation::validate()const "Asset type of repay amount and credit fee should be the same" ); } +void credit_deal_update_operation::validate()const +{ + FC_ASSERT( fee.amount >= 0, "Fee should not be negative" ); + + constexpr auto cdar_count = static_cast( credit_deal_auto_repayment_type::CDAR_TYPE_COUNT ); + FC_ASSERT( auto_repay < cdar_count, + "auto_repay should be less than ${c}", ("c",cdar_count) ); +} + } } // graphene::protocol -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_create_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_delete_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_update_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_accept_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_repay_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_create_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_delete_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_update_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_accept_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_repay_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_update_operation::fee_params_t ) + +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_accept_operation::ext ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_create_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_delete_operation ) @@ -171,3 +189,4 @@ GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_upda GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_accept_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_repay_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_expired_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_update_operation ) diff --git a/libraries/protocol/custom.cpp b/libraries/protocol/custom.cpp index 22ea61f72b..07165b4bec 100644 --- a/libraries/protocol/custom.cpp +++ b/libraries/protocol/custom.cpp @@ -31,12 +31,12 @@ void custom_operation::validate()const { FC_ASSERT( fee.amount > 0 ); } -share_type custom_operation::calculate_fee(const fee_parameters_type& k)const +share_type custom_operation::calculate_fee(const fee_params_t& k)const { return k.fee + calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte ); } } } -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::custom_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::custom_operation::fee_params_t ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::custom_operation ) diff --git a/libraries/protocol/custom_authority.cpp b/libraries/protocol/custom_authority.cpp index f7217d01f2..45eff2ab52 100644 --- a/libraries/protocol/custom_authority.cpp +++ b/libraries/protocol/custom_authority.cpp @@ -29,7 +29,7 @@ namespace graphene { namespace protocol { -share_type custom_authority_create_operation::calculate_fee(const fee_parameters_type& k)const { +share_type custom_authority_create_operation::calculate_fee(const fee_params_t& k)const { share_type core_fee_required = k.basic_fee; // Note: practically the `*` won't cause an integer overflow, because k.price_per_byte is 32 bit // and the results of pack_size() won't be too big @@ -58,7 +58,7 @@ void custom_authority_create_operation::validate()const { get_restriction_predicate(restrictions, operation_type); } -share_type custom_authority_update_operation::calculate_fee(const fee_parameters_type& k)const { +share_type custom_authority_update_operation::calculate_fee(const fee_params_t& k)const { share_type core_fee_required = k.basic_fee; // Note: practically the `*` won't cause an integer overflow, because k.price_per_byte is 32 bit // and the results of pack_size() won't be too big diff --git a/libraries/protocol/fee_schedule_calc.cpp b/libraries/protocol/fee_schedule_calc.cpp index ccb976da4c..e92710f106 100644 --- a/libraries/protocol/fee_schedule_calc.cpp +++ b/libraries/protocol/fee_schedule_calc.cpp @@ -48,7 +48,7 @@ namespace graphene { namespace protocol { auto itr = param.parameters.find(params); if( itr != param.parameters.end() ) params = *itr; - return op.calculate_fee( params.get() ).value; + return op.calculate_fee( params.get() ).value; } } }; @@ -57,7 +57,7 @@ namespace graphene { namespace protocol { uint64_t calc_fee_visitor::operator()(const htlc_create_operation& op)const { //TODO: refactor for performance (see https://github.com/bitshares/bitshares-core/issues/2150) - transfer_operation::fee_parameters_type t; + transfer_operation::fee_params_t t; if (param.exists()) t = param.get(); return op.calculate_fee( param.get(), t.price_per_kbyte).value; @@ -70,7 +70,7 @@ namespace graphene { namespace protocol { optional sub_asset_creation_fee; if( param.exists() && param.exists() ) sub_asset_creation_fee = param.get().fee; - asset_create_operation::fee_parameters_type old_asset_creation_fee_params; + asset_create_operation::fee_params_t old_asset_creation_fee_params; if( param.exists() ) old_asset_creation_fee_params = param.get(); return op.calculate_fee( old_asset_creation_fee_params, sub_asset_creation_fee ).value; diff --git a/libraries/protocol/htlc.cpp b/libraries/protocol/htlc.cpp index 5694ec5a26..13d91f6c72 100644 --- a/libraries/protocol/htlc.cpp +++ b/libraries/protocol/htlc.cpp @@ -34,7 +34,7 @@ namespace graphene { namespace protocol { FC_ASSERT( amount.amount > 0, "HTLC amount should be greater than zero" ); } - share_type htlc_create_operation::calculate_fee( const fee_parameters_type& fee_params, + share_type htlc_create_operation::calculate_fee( const fee_params_t& fee_params, uint32_t fee_per_kb )const { uint64_t days = ( claim_period_seconds + SECONDS_PER_DAY - 1 ) / SECONDS_PER_DAY; @@ -50,7 +50,7 @@ namespace graphene { namespace protocol { FC_ASSERT( fee.amount >= 0, "Fee amount should not be negative" ); } - share_type htlc_redeem_operation::calculate_fee( const fee_parameters_type& fee_params )const + share_type htlc_redeem_operation::calculate_fee( const fee_params_t& fee_params )const { uint64_t kb = ( preimage.size() + 1023 ) / 1024; uint64_t product = kb * fee_params.fee_per_kb; @@ -62,7 +62,7 @@ namespace graphene { namespace protocol { FC_ASSERT( fee.amount >= 0 , "Fee amount should not be negative"); } - share_type htlc_extend_operation::calculate_fee( const fee_parameters_type& fee_params )const + share_type htlc_extend_operation::calculate_fee( const fee_params_t& fee_params )const { uint32_t days = ( seconds_to_add + SECONDS_PER_DAY - 1 ) / SECONDS_PER_DAY; uint64_t per_day_fee = fee_params.fee_per_day * days; @@ -71,10 +71,10 @@ namespace graphene { namespace protocol { } } } -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation::fee_params_t ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation::additional_options_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_redeem_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_extend_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_redeem_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_extend_operation::fee_params_t ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_redeem_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_redeemed_operation ) diff --git a/libraries/protocol/include/graphene/protocol/account.hpp b/libraries/protocol/include/graphene/protocol/account.hpp index b7ab4c10c6..800201654f 100644 --- a/libraries/protocol/include/graphene/protocol/account.hpp +++ b/libraries/protocol/include/graphene/protocol/account.hpp @@ -88,7 +88,7 @@ namespace graphene { namespace protocol { optional< buyback_account_options > buyback_options; }; - struct fee_parameters_type + struct fee_params_t { uint64_t basic_fee = 5*GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to register the cheapest non-free account uint64_t premium_fee = 2000*GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to register the cheapest non-free account @@ -114,7 +114,7 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return registrar; } void validate()const; - share_type calculate_fee(const fee_parameters_type& )const; + share_type calculate_fee(const fee_params_t& )const; void get_required_active_authorities( flat_set& a )const { @@ -142,7 +142,7 @@ namespace graphene { namespace protocol { optional< special_authority > active_special_authority; }; - struct fee_parameters_type + struct fee_params_t { share_type fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION; @@ -163,7 +163,7 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return account; } void validate()const; - share_type calculate_fee( const fee_parameters_type& k )const; + share_type calculate_fee( const fee_params_t& k )const; bool is_owner_update()const { return owner || extensions.value.owner_special_authority.valid(); } @@ -196,7 +196,7 @@ namespace graphene { namespace protocol { */ struct account_whitelist_operation : public base_operation { - struct fee_parameters_type { share_type fee = 300000; }; + struct fee_params_t { share_type fee = 300000; }; enum account_listing { no_listing = 0x0, ///< No opinion is specified about this account white_listed = 0x1, ///< This account is whitelisted, but not blacklisted @@ -234,7 +234,7 @@ namespace graphene { namespace protocol { */ struct account_upgrade_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t membership_annual_fee = 2000 * GRAPHENE_BLOCKCHAIN_PRECISION; uint64_t membership_lifetime_fee = 10000 * GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to upgrade to a lifetime member }; @@ -248,7 +248,7 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return account_to_upgrade; } void validate()const; - share_type calculate_fee( const fee_parameters_type& k )const; + share_type calculate_fee( const fee_params_t& k )const; }; /** @@ -266,7 +266,7 @@ namespace graphene { namespace protocol { */ struct account_transfer_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; account_id_type account_id; @@ -302,20 +302,20 @@ FC_REFLECT( graphene::protocol::account_upgrade_operation, 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_create_operation::fee_params_t, (basic_fee)(premium_fee)(price_per_kbyte) ) +FC_REFLECT( graphene::protocol::account_whitelist_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::account_update_operation::fee_params_t, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::protocol::account_upgrade_operation::fee_params_t, (membership_annual_fee)(membership_lifetime_fee) ) +FC_REFLECT( graphene::protocol::account_transfer_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::account_transfer_operation, (fee)(account_id)(new_owner)(extensions) ) 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_transfer_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_create_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_whitelist_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_update_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_upgrade_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_transfer_operation::fee_params_t ) 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 ) diff --git a/libraries/protocol/include/graphene/protocol/assert.hpp b/libraries/protocol/include/graphene/protocol/assert.hpp index fead1a7031..5c0e4105ba 100644 --- a/libraries/protocol/include/graphene/protocol/assert.hpp +++ b/libraries/protocol/include/graphene/protocol/assert.hpp @@ -92,7 +92,7 @@ namespace graphene { namespace protocol { */ struct assert_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; account_id_type fee_paying_account; @@ -102,17 +102,17 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return fee_paying_account; } void validate()const; - share_type calculate_fee(const fee_parameters_type& k)const; + share_type calculate_fee(const fee_params_t& k)const; }; } } // graphene::protocol -FC_REFLECT( graphene::protocol::assert_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::assert_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::account_name_eq_lit_predicate, (account_id)(name) ) FC_REFLECT( graphene::protocol::asset_symbol_eq_lit_predicate, (asset_id)(symbol) ) FC_REFLECT( graphene::protocol::block_id_predicate, (id) ) FC_REFLECT_TYPENAME( graphene::protocol::predicate ) FC_REFLECT( graphene::protocol::assert_operation, (fee)(fee_paying_account)(predicates)(required_auths)(extensions) ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::assert_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::assert_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::assert_operation ) diff --git a/libraries/protocol/include/graphene/protocol/asset_ops.hpp b/libraries/protocol/include/graphene/protocol/asset_ops.hpp index 43eec5564d..afca254425 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -191,7 +191,7 @@ namespace graphene { namespace protocol { */ struct asset_create_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t symbol3 = 500000 * GRAPHENE_BLOCKCHAIN_PRECISION; uint64_t symbol4 = 300000 * GRAPHENE_BLOCKCHAIN_PRECISION; uint64_t long_symbol = 5000 * GRAPHENE_BLOCKCHAIN_PRECISION; @@ -221,7 +221,7 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return issuer; } void validate()const; - share_type calculate_fee( const fee_parameters_type& k, + share_type calculate_fee( const fee_params_t& k, const optional& sub_asset_creation_fee )const; }; @@ -237,7 +237,7 @@ namespace graphene { namespace protocol { */ struct asset_global_settle_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; account_id_type issuer; ///< must equal issuer of @ref asset_to_settle @@ -266,7 +266,7 @@ namespace graphene { namespace protocol { */ struct asset_settle_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { /** this fee should be high to encourage small settlement requests to * be performed on the market rather than via forced settlement. * @@ -292,7 +292,7 @@ namespace graphene { namespace protocol { */ struct asset_settle_cancel_operation : public base_operation { - struct fee_parameters_type { }; + struct fee_params_t { }; asset_settle_cancel_operation() = default; asset_settle_cancel_operation( const force_settlement_id_type& fsid, const account_id_type& aid, @@ -312,7 +312,7 @@ namespace graphene { namespace protocol { */ void validate() const { FC_ASSERT( !"Virtual operation"); } - share_type calculate_fee(const fee_parameters_type& params)const + share_type calculate_fee(const fee_params_t& params)const { return 0; } }; @@ -321,7 +321,7 @@ namespace graphene { namespace protocol { */ struct asset_fund_fee_pool_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; ///< core asset account_id_type from_account; @@ -360,7 +360,7 @@ namespace graphene { namespace protocol { fc::optional skip_core_exchange_rate; }; - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; uint32_t price_per_kbyte = 10; }; @@ -378,7 +378,7 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return issuer; } void validate()const; - share_type calculate_fee(const fee_parameters_type& k)const; + share_type calculate_fee(const fee_params_t& k)const; }; /** @@ -397,7 +397,7 @@ namespace graphene { namespace protocol { */ struct asset_update_bitasset_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; account_id_type issuer; @@ -429,7 +429,7 @@ namespace graphene { namespace protocol { */ struct asset_update_feed_producers_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; account_id_type issuer; @@ -467,7 +467,7 @@ namespace graphene { namespace protocol { fc::optional initial_collateral_ratio; // BSIP-77 }; - struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; ///< paid for by publisher account_id_type publisher; @@ -484,7 +484,7 @@ namespace graphene { namespace protocol { */ struct asset_issue_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION; }; @@ -501,7 +501,7 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return issuer; } void validate()const; - share_type calculate_fee(const fee_parameters_type& k)const; + share_type calculate_fee(const fee_params_t& k)const; }; /** @@ -512,7 +512,7 @@ namespace graphene { namespace protocol { */ struct asset_reserve_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; account_id_type payer; @@ -528,7 +528,7 @@ namespace graphene { namespace protocol { */ struct asset_claim_fees_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; }; @@ -564,7 +564,7 @@ namespace graphene { namespace protocol { */ struct asset_update_issuer_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; }; @@ -600,7 +600,7 @@ namespace graphene { namespace protocol { */ struct asset_claim_pool_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; }; @@ -617,11 +617,11 @@ namespace graphene { namespace protocol { } } // graphene::protocol FC_REFLECT( graphene::protocol::asset_claim_fees_operation, (fee)(issuer)(amount_to_claim)(extensions) ) -FC_REFLECT( graphene::protocol::asset_claim_fees_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::asset_claim_fees_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::asset_claim_fees_operation::additional_options_type, (claim_from_asset_id) ) FC_REFLECT( graphene::protocol::asset_claim_pool_operation, (fee)(issuer)(asset_id)(amount_to_claim)(extensions) ) -FC_REFLECT( graphene::protocol::asset_claim_pool_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::asset_claim_pool_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::asset_options, (max_supply) @@ -663,20 +663,20 @@ FC_REFLECT( graphene::protocol::additional_asset_options, FC_REFLECT( graphene::protocol::asset_update_operation::ext, (new_precision)(skip_core_exchange_rate) ) FC_REFLECT( graphene::protocol::asset_publish_feed_operation::ext, (initial_collateral_ratio) ) -FC_REFLECT( graphene::protocol::asset_create_operation::fee_parameters_type, +FC_REFLECT( graphene::protocol::asset_create_operation::fee_params_t, (symbol3)(symbol4)(long_symbol)(price_per_kbyte) ) -FC_REFLECT( graphene::protocol::asset_global_settle_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::asset_settle_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::asset_settle_cancel_operation::fee_parameters_type, ) -FC_REFLECT( graphene::protocol::asset_fund_fee_pool_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::asset_update_operation::fee_parameters_type, (fee)(price_per_kbyte) ) -FC_REFLECT( graphene::protocol::asset_update_issuer_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::asset_update_bitasset_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::asset_update_feed_producers_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::asset_publish_feed_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::asset_issue_operation::fee_parameters_type, (fee)(price_per_kbyte) ) -FC_REFLECT( graphene::protocol::asset_reserve_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::asset_global_settle_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::asset_settle_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::asset_settle_cancel_operation::fee_params_t, ) +FC_REFLECT( graphene::protocol::asset_fund_fee_pool_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::asset_update_operation::fee_params_t, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::protocol::asset_update_issuer_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::asset_update_bitasset_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::asset_update_feed_producers_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::asset_publish_feed_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::asset_issue_operation::fee_params_t, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::protocol::asset_reserve_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::asset_create_operation, @@ -735,21 +735,21 @@ GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::additional_asset_op GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_operation::ext ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_publish_feed_operation::ext ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_create_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_global_settle_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_settle_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_fund_fee_pool_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_pool_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_create_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_global_settle_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_settle_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_fund_fee_pool_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_pool_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation::additional_options_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_issuer_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_bitasset_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_issuer_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_bitasset_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( - graphene::protocol::asset_update_feed_producers_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_publish_feed_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_issue_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_reserve_operation::fee_parameters_type ) + graphene::protocol::asset_update_feed_producers_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_publish_feed_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_issue_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_reserve_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_create_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_global_settle_operation ) diff --git a/libraries/protocol/include/graphene/protocol/balance.hpp b/libraries/protocol/include/graphene/protocol/balance.hpp index 825bcce95d..457ccdd889 100644 --- a/libraries/protocol/include/graphene/protocol/balance.hpp +++ b/libraries/protocol/include/graphene/protocol/balance.hpp @@ -39,7 +39,7 @@ namespace graphene { namespace protocol { */ struct balance_claim_operation : public base_operation { - struct fee_parameters_type {}; + struct fee_params_t {}; asset fee; account_id_type deposit_to_account; @@ -48,7 +48,7 @@ namespace graphene { namespace protocol { asset total_claimed; account_id_type fee_payer()const { return deposit_to_account; } - share_type calculate_fee(const fee_parameters_type& )const { return 0; } + share_type calculate_fee(const fee_params_t& )const { return 0; } void validate()const; void get_required_authorities( vector& a )const { @@ -58,7 +58,7 @@ namespace graphene { namespace protocol { } } // graphene::protocol -FC_REFLECT( graphene::protocol::balance_claim_operation::fee_parameters_type, ) +FC_REFLECT( graphene::protocol::balance_claim_operation::fee_params_t, ) FC_REFLECT( graphene::protocol::balance_claim_operation, (fee)(deposit_to_account)(balance_to_claim)(balance_owner_key)(total_claimed) ) diff --git a/libraries/protocol/include/graphene/protocol/committee_member.hpp b/libraries/protocol/include/graphene/protocol/committee_member.hpp index 2bd563800e..3fdfb1c53b 100644 --- a/libraries/protocol/include/graphene/protocol/committee_member.hpp +++ b/libraries/protocol/include/graphene/protocol/committee_member.hpp @@ -32,12 +32,12 @@ namespace graphene { namespace protocol { * @brief Create a committee_member object, as a bid to hold a committee_member seat on the network. * @ingroup operations * - * Accounts which wish to become committee_members may use this operation to create a committee_member object which stakeholders may - * vote on to approve its position as a committee_member. + * Accounts which wish to become committee_members may use this operation to create a committee_member object + * which stakeholders may vote on to approve its position as a committee_member. */ struct committee_member_create_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 5000 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 5000 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; /// The account which owns the committee_member. This account pays the fee for this operation. @@ -57,7 +57,7 @@ namespace graphene { namespace protocol { */ struct committee_member_update_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; /// The committee member to update. @@ -74,16 +74,16 @@ namespace graphene { namespace protocol { * @brief Used by committee_members to update the global parameters of the blockchain. * @ingroup operations * - * This operation allows the committee_members to update the global parameters on the blockchain. These control various - * tunable aspects of the chain, including block and maintenance intervals, maximum data sizes, the fees charged by - * the network, etc. + * This operation allows the committee_members to update the global parameters on the blockchain. + * These control various tunable aspects of the chain, including block and maintenance intervals, + * maximum data sizes, the fees charged by the network, etc. * * This operation may only be used in a proposed transaction, and a proposed transaction which contains this * operation must have a review period specified in the current global parameters before it may be accepted. */ struct committee_member_update_global_parameters_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; chain_parameters new_parameters; @@ -96,9 +96,9 @@ namespace graphene { namespace protocol { } } // graphene::protocol -FC_REFLECT( graphene::protocol::committee_member_create_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::committee_member_update_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::committee_member_update_global_parameters_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::committee_member_create_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::committee_member_update_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::committee_member_update_global_parameters_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::committee_member_create_operation, (fee)(committee_member_account)(url) ) @@ -106,9 +106,11 @@ FC_REFLECT( graphene::protocol::committee_member_update_operation, (fee)(committee_member)(committee_member_account)(new_url) ) FC_REFLECT( graphene::protocol::committee_member_update_global_parameters_operation, (fee)(new_parameters) ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_create_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_update_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_update_global_parameters_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_create_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_update_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( + graphene::protocol::committee_member_update_global_parameters_operation::fee_params_t ) + GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_create_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_update_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::committee_member_update_global_parameters_operation ) diff --git a/libraries/protocol/include/graphene/protocol/confidential.hpp b/libraries/protocol/include/graphene/protocol/confidential.hpp index 5015b98002..8e9b528fad 100644 --- a/libraries/protocol/include/graphene/protocol/confidential.hpp +++ b/libraries/protocol/include/graphene/protocol/confidential.hpp @@ -149,7 +149,7 @@ struct blind_output */ struct transfer_to_blind_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = 5*GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to register the cheapest non-free account uint32_t price_per_output = 5*GRAPHENE_BLOCKCHAIN_PRECISION; }; @@ -163,7 +163,7 @@ struct transfer_to_blind_operation : public base_operation account_id_type fee_payer()const { return from; } void validate()const; - share_type calculate_fee(const fee_parameters_type& )const; + share_type calculate_fee(const fee_params_t& )const; }; /** @@ -172,7 +172,7 @@ struct transfer_to_blind_operation : public base_operation */ struct transfer_from_blind_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = 5*GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to register the cheapest non-free account }; @@ -237,7 +237,7 @@ struct transfer_from_blind_operation : public base_operation */ struct blind_transfer_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = 5*GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to register the cheapest non-free account uint32_t price_per_output = 5*GRAPHENE_BLOCKCHAIN_PRECISION; }; @@ -249,7 +249,7 @@ struct blind_transfer_operation : public base_operation /** graphene TEMP account */ account_id_type fee_payer()const; void validate()const; - share_type calculate_fee( const fee_parameters_type& k )const; + share_type calculate_fee( const fee_params_t& k )const; void get_required_authorities( vector& a )const { @@ -280,13 +280,13 @@ FC_REFLECT( graphene::protocol::transfer_from_blind_operation, (fee)(amount)(to)(blinding_factor)(inputs) ) FC_REFLECT( graphene::protocol::blind_transfer_operation, (fee)(inputs)(outputs) ) -FC_REFLECT( graphene::protocol::transfer_to_blind_operation::fee_parameters_type, (fee)(price_per_output) ) -FC_REFLECT( graphene::protocol::transfer_from_blind_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::blind_transfer_operation::fee_parameters_type, (fee)(price_per_output) ) +FC_REFLECT( graphene::protocol::transfer_to_blind_operation::fee_params_t, (fee)(price_per_output) ) +FC_REFLECT( graphene::protocol::transfer_from_blind_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::blind_transfer_operation::fee_params_t, (fee)(price_per_output) ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_to_blind_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_from_blind_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::blind_transfer_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_to_blind_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_from_blind_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::blind_transfer_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_to_blind_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_from_blind_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::blind_transfer_operation ) diff --git a/libraries/protocol/include/graphene/protocol/credit_offer.hpp b/libraries/protocol/include/graphene/protocol/credit_offer.hpp index 3e9fe72244..a54178ce77 100644 --- a/libraries/protocol/include/graphene/protocol/credit_offer.hpp +++ b/libraries/protocol/include/graphene/protocol/credit_offer.hpp @@ -35,7 +35,7 @@ namespace graphene { namespace protocol { */ struct credit_offer_create_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; uint32_t price_per_kbyte = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; @@ -60,7 +60,7 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return owner_account; } void validate()const override; - share_type calculate_fee(const fee_parameters_type& k)const; + share_type calculate_fee(const fee_params_t& k)const; }; /** @@ -69,7 +69,7 @@ namespace graphene { namespace protocol { */ struct credit_offer_delete_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 0; }; + struct fee_params_t { uint64_t fee = 0; }; asset fee; ///< Operation fee account_id_type owner_account; ///< The account who owns the credit offer @@ -87,7 +87,7 @@ namespace graphene { namespace protocol { */ struct credit_offer_update_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; uint32_t price_per_kbyte = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; @@ -112,16 +112,35 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return owner_account; } void validate()const override; - share_type calculate_fee(const fee_parameters_type& k)const; + share_type calculate_fee(const fee_params_t& k)const; + }; + + /// Defines automatic repayment types + enum class credit_deal_auto_repayment_type + { + /// Do not repay automatically + no_auto_repayment = 0, + /// Automatically repay fully when and only when the account balance is sufficient + only_full_repayment = 1, + /// Automatically repay as much as possible using available account balance + allow_partial_repayment = 2, + /// Total number of available automatic repayment types + CDAR_TYPE_COUNT = 3 }; /** - * @brief Accept a creadit offer and create a credit deal + * @brief Accept a credit offer, thereby creating a credit deal * @ingroup operations */ struct credit_offer_accept_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct ext + { + /// After the core-2595 hard fork, the account can specify whether and how to automatically repay + fc::optional auto_repay; + }; + + struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; ///< Operation fee account_id_type borrower; ///< The account who accepts the offer @@ -131,7 +150,7 @@ namespace graphene { namespace protocol { uint32_t max_fee_rate = 0; ///< The maximum acceptable fee rate uint32_t min_duration_seconds = 0; ///< The minimum acceptable duration - extensions_type extensions; ///< Unused. Reserved for future use. + extension extensions; ///< Extensions account_id_type fee_payer()const { return borrower; } void validate()const override; @@ -143,7 +162,7 @@ namespace graphene { namespace protocol { */ struct credit_deal_repay_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; ///< Operation fee account_id_type account; ///< The account who repays to the credit offer @@ -164,7 +183,7 @@ namespace graphene { namespace protocol { */ struct credit_deal_expired_operation : public base_operation { - struct fee_parameters_type {}; + struct fee_params_t {}; credit_deal_expired_operation() = default; @@ -186,17 +205,37 @@ namespace graphene { namespace protocol { void validate()const override { FC_ASSERT( !"virtual operation" ); } /// This is a virtual operation; there is no fee - share_type calculate_fee(const fee_parameters_type&)const { return 0; } + share_type calculate_fee(const fee_params_t&)const { return 0; } + }; + + /** + * @brief Update a credit deal + * @ingroup operations + */ + struct credit_deal_update_operation : public base_operation + { + struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; ///< Operation fee + account_id_type account; ///< The account who owns the credit deal + credit_deal_id_type deal_id; ///< ID of the credit deal + uint8_t auto_repay; ///< The specified automatic repayment type + + extensions_type extensions; ///< Unused. Reserved for future use. + + account_id_type fee_payer()const { return account; } + void validate()const override; }; } } // graphene::protocol -FC_REFLECT( graphene::protocol::credit_offer_create_operation::fee_parameters_type, (fee)(price_per_kbyte) ) -FC_REFLECT( graphene::protocol::credit_offer_delete_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::credit_offer_update_operation::fee_parameters_type, (fee)(price_per_kbyte) ) -FC_REFLECT( graphene::protocol::credit_offer_accept_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::credit_deal_repay_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::credit_deal_expired_operation::fee_parameters_type, ) // VIRTUAL +FC_REFLECT( graphene::protocol::credit_offer_create_operation::fee_params_t, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::protocol::credit_offer_delete_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::credit_offer_update_operation::fee_params_t, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::protocol::credit_offer_accept_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::credit_deal_repay_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::credit_deal_expired_operation::fee_params_t, ) // VIRTUAL +FC_REFLECT( graphene::protocol::credit_deal_update_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::credit_offer_create_operation, (fee) @@ -235,6 +274,10 @@ FC_REFLECT( graphene::protocol::credit_offer_update_operation, (extensions) ) +FC_REFLECT( graphene::protocol::credit_offer_accept_operation::ext, + (auto_repay) + ) + FC_REFLECT( graphene::protocol::credit_offer_accept_operation, (fee) (borrower) @@ -266,12 +309,23 @@ FC_REFLECT( graphene::protocol::credit_deal_expired_operation, (fee_rate) ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_create_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_delete_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_update_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_accept_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_repay_operation::fee_parameters_type ) -// Note: credit_deal_expired_operation is virtual so no external serialization for its fee_parameters_type +FC_REFLECT( graphene::protocol::credit_deal_update_operation, + (fee) + (account) + (deal_id) + (auto_repay) + (extensions) + ) + +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_accept_operation::ext ) + +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_create_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_delete_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_update_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_accept_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_repay_operation::fee_params_t ) +// Note: credit_deal_expired_operation is virtual so no external serialization for its fee_params_t +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_update_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_create_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_delete_operation ) @@ -279,3 +333,4 @@ GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_update GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_accept_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_repay_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_expired_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_update_operation ) diff --git a/libraries/protocol/include/graphene/protocol/custom.hpp b/libraries/protocol/include/graphene/protocol/custom.hpp index 59ef3757db..49c552069b 100644 --- a/libraries/protocol/include/graphene/protocol/custom.hpp +++ b/libraries/protocol/include/graphene/protocol/custom.hpp @@ -37,7 +37,7 @@ namespace graphene { namespace protocol { */ struct custom_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; uint32_t price_per_kbyte = 10; }; @@ -50,7 +50,7 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return payer; } void validate()const; - share_type calculate_fee(const fee_parameters_type& k)const; + share_type calculate_fee(const fee_params_t& k)const; void get_required_active_authorities( flat_set& auths )const { auths.insert( required_auths.begin(), required_auths.end() ); } @@ -58,8 +58,8 @@ namespace graphene { namespace protocol { } } // namespace graphene::protocol -FC_REFLECT( graphene::protocol::custom_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::protocol::custom_operation::fee_params_t, (fee)(price_per_kbyte) ) FC_REFLECT( graphene::protocol::custom_operation, (fee)(payer)(required_auths)(id)(data) ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::custom_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::custom_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::custom_operation ) diff --git a/libraries/protocol/include/graphene/protocol/custom_authority.hpp b/libraries/protocol/include/graphene/protocol/custom_authority.hpp index 7448c84aee..54aa929285 100644 --- a/libraries/protocol/include/graphene/protocol/custom_authority.hpp +++ b/libraries/protocol/include/graphene/protocol/custom_authority.hpp @@ -34,7 +34,7 @@ namespace graphene { namespace protocol { * @ingroup operations */ struct custom_authority_create_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t basic_fee = GRAPHENE_BLOCKCHAIN_PRECISION; uint32_t price_per_byte = GRAPHENE_BLOCKCHAIN_PRECISION / 10; }; @@ -60,7 +60,7 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return account; } void validate()const; - share_type calculate_fee(const fee_parameters_type& k)const; + share_type calculate_fee(const fee_params_t& k)const; }; /** @@ -68,7 +68,7 @@ namespace graphene { namespace protocol { * @ingroup operations */ struct custom_authority_update_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t basic_fee = GRAPHENE_BLOCKCHAIN_PRECISION; uint32_t price_per_byte = GRAPHENE_BLOCKCHAIN_PRECISION / 10; }; @@ -96,7 +96,7 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return account; } void validate()const; - share_type calculate_fee(const fee_parameters_type& k)const; + share_type calculate_fee(const fee_params_t& k)const; }; @@ -105,7 +105,7 @@ namespace graphene { namespace protocol { * @ingroup operations */ struct custom_authority_delete_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; /// Operation fee asset fee; @@ -118,14 +118,14 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return account; } void validate()const; - share_type calculate_fee(const fee_parameters_type& k)const { return k.fee; } + share_type calculate_fee(const fee_params_t& k)const { return k.fee; } }; } } // graphene::protocol -FC_REFLECT(graphene::protocol::custom_authority_create_operation::fee_parameters_type, (basic_fee)(price_per_byte)) -FC_REFLECT(graphene::protocol::custom_authority_update_operation::fee_parameters_type, (basic_fee)(price_per_byte)) -FC_REFLECT(graphene::protocol::custom_authority_delete_operation::fee_parameters_type, (fee)) +FC_REFLECT(graphene::protocol::custom_authority_create_operation::fee_params_t, (basic_fee)(price_per_byte)) +FC_REFLECT(graphene::protocol::custom_authority_update_operation::fee_params_t, (basic_fee)(price_per_byte)) +FC_REFLECT(graphene::protocol::custom_authority_delete_operation::fee_params_t, (fee)) FC_REFLECT(graphene::protocol::custom_authority_create_operation, (fee)(account)(enabled)(valid_from)(valid_to)(operation_type)(auth)(restrictions)(extensions)) diff --git a/libraries/protocol/include/graphene/protocol/fba.hpp b/libraries/protocol/include/graphene/protocol/fba.hpp index 0c58c2eb53..3330c8a655 100644 --- a/libraries/protocol/include/graphene/protocol/fba.hpp +++ b/libraries/protocol/include/graphene/protocol/fba.hpp @@ -29,7 +29,7 @@ namespace graphene { namespace protocol { struct fba_distribute_operation : public base_operation { - struct fee_parameters_type {}; + struct fee_params_t {}; asset fee; // always zero account_id_type account_id; @@ -39,12 +39,12 @@ struct fba_distribute_operation : public base_operation account_id_type fee_payer()const { return account_id; } void validate()const { FC_ASSERT( false ); } - share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + share_type calculate_fee(const fee_params_t& k)const { return 0; } }; } } -FC_REFLECT( graphene::protocol::fba_distribute_operation::fee_parameters_type, ) +FC_REFLECT( graphene::protocol::fba_distribute_operation::fee_params_t, ) FC_REFLECT( graphene::protocol::fba_distribute_operation, (fee)(account_id)(fba_id)(amount) ) diff --git a/libraries/protocol/include/graphene/protocol/fee_schedule.hpp b/libraries/protocol/include/graphene/protocol/fee_schedule.hpp index f3adc6840c..45f72b50d3 100644 --- a/libraries/protocol/include/graphene/protocol/fee_schedule.hpp +++ b/libraries/protocol/include/graphene/protocol/fee_schedule.hpp @@ -30,48 +30,48 @@ namespace graphene { namespace protocol { template struct transform_to_fee_parameters> { - using type = fc::static_variant< typename T::fee_parameters_type... >; + using type = fc::static_variant< typename T::fee_params_t... >; }; using fee_parameters = transform_to_fee_parameters::type; template class fee_helper { public: - const typename Operation::fee_parameters_type& cget(const fee_parameters::flat_set_type& parameters)const + const typename Operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const { - auto itr = parameters.find( typename Operation::fee_parameters_type() ); + auto itr = parameters.find( typename Operation::fee_params_t() ); FC_ASSERT( itr != parameters.end() ); - return itr->template get(); + return itr->template get(); } }; template<> class fee_helper { public: - const account_create_operation::fee_parameters_type& cget(const fee_parameters::flat_set_type& parameters)const + const account_create_operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const { - auto itr = parameters.find( account_create_operation::fee_parameters_type() ); + auto itr = parameters.find( account_create_operation::fee_params_t() ); FC_ASSERT( itr != parameters.end() ); - return itr->get(); + return itr->get(); } - typename account_create_operation::fee_parameters_type& get(fee_parameters::flat_set_type& parameters)const + typename account_create_operation::fee_params_t& get(fee_parameters::flat_set_type& parameters)const { - auto itr = parameters.find( account_create_operation::fee_parameters_type() ); + auto itr = parameters.find( account_create_operation::fee_params_t() ); FC_ASSERT( itr != parameters.end() ); - return itr->get(); + return itr->get(); } }; template<> class fee_helper { public: - const bid_collateral_operation::fee_parameters_type& cget(const fee_parameters::flat_set_type& parameters)const + const bid_collateral_operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const { - auto itr = parameters.find( bid_collateral_operation::fee_parameters_type() ); + auto itr = parameters.find( bid_collateral_operation::fee_params_t() ); if ( itr != parameters.end() ) - return itr->get(); + return itr->get(); - static bid_collateral_operation::fee_parameters_type bid_collateral_dummy; + static bid_collateral_operation::fee_params_t bid_collateral_dummy; bid_collateral_dummy.fee = fee_helper().cget(parameters).fee; return bid_collateral_dummy; } @@ -80,13 +80,13 @@ namespace graphene { namespace protocol { template<> class fee_helper { public: - const asset_update_issuer_operation::fee_parameters_type& cget(const fee_parameters::flat_set_type& parameters)const + const asset_update_issuer_operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const { - auto itr = parameters.find( asset_update_issuer_operation::fee_parameters_type() ); + auto itr = parameters.find( asset_update_issuer_operation::fee_params_t() ); if ( itr != parameters.end() ) - return itr->get(); + return itr->get(); - static asset_update_issuer_operation::fee_parameters_type dummy; + static asset_update_issuer_operation::fee_params_t dummy; dummy.fee = fee_helper().cget(parameters).fee; return dummy; } @@ -95,13 +95,13 @@ namespace graphene { namespace protocol { template<> class fee_helper { public: - const asset_claim_pool_operation::fee_parameters_type& cget(const fee_parameters::flat_set_type& parameters)const + const asset_claim_pool_operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const { - auto itr = parameters.find( asset_claim_pool_operation::fee_parameters_type() ); + auto itr = parameters.find( asset_claim_pool_operation::fee_params_t() ); if ( itr != parameters.end() ) - return itr->get(); + return itr->get(); - static asset_claim_pool_operation::fee_parameters_type asset_claim_pool_dummy; + static asset_claim_pool_operation::fee_params_t asset_claim_pool_dummy; asset_claim_pool_dummy.fee = fee_helper().cget(parameters).fee; return asset_claim_pool_dummy; } @@ -110,9 +110,9 @@ namespace graphene { namespace protocol { template<> class fee_helper { public: - const ticket_create_operation::fee_parameters_type& cget(const fee_parameters::flat_set_type& parameters)const + const ticket_create_operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const { - static ticket_create_operation::fee_parameters_type param; + static ticket_create_operation::fee_params_t param; return param; } }; @@ -120,9 +120,9 @@ namespace graphene { namespace protocol { template<> class fee_helper { public: - const ticket_update_operation::fee_parameters_type& cget(const fee_parameters::flat_set_type& parameters)const + const ticket_update_operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const { - static ticket_update_operation::fee_parameters_type param; + static ticket_update_operation::fee_params_t param; return param; } }; @@ -130,13 +130,13 @@ namespace graphene { namespace protocol { template<> class fee_helper { public: - const htlc_create_operation::fee_parameters_type& cget(const fee_parameters::flat_set_type& parameters)const + const htlc_create_operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const { - auto itr = parameters.find( htlc_create_operation::fee_parameters_type() ); + auto itr = parameters.find( htlc_create_operation::fee_params_t() ); if ( itr != parameters.end() ) - return itr->get(); + return itr->get(); - static htlc_create_operation::fee_parameters_type htlc_create_operation_fee_dummy; + static htlc_create_operation::fee_params_t htlc_create_operation_fee_dummy; return htlc_create_operation_fee_dummy; } }; @@ -144,26 +144,26 @@ namespace graphene { namespace protocol { template<> class fee_helper { public: - const htlc_redeem_operation::fee_parameters_type& cget(const fee_parameters::flat_set_type& parameters)const + const htlc_redeem_operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const { - auto itr = parameters.find( htlc_redeem_operation::fee_parameters_type() ); + auto itr = parameters.find( htlc_redeem_operation::fee_params_t() ); if ( itr != parameters.end() ) - return itr->get(); + return itr->get(); - static htlc_redeem_operation::fee_parameters_type htlc_redeem_operation_fee_dummy; + static htlc_redeem_operation::fee_params_t htlc_redeem_operation_fee_dummy; return htlc_redeem_operation_fee_dummy; } }; template<> class fee_helper { public: - const htlc_extend_operation::fee_parameters_type& cget(const fee_parameters::flat_set_type& parameters)const + const htlc_extend_operation::fee_params_t& cget(const fee_parameters::flat_set_type& parameters)const { - auto itr = parameters.find( htlc_extend_operation::fee_parameters_type() ); + auto itr = parameters.find( htlc_extend_operation::fee_params_t() ); if ( itr != parameters.end() ) - return itr->get(); + return itr->get(); - static htlc_extend_operation::fee_parameters_type htlc_extend_operation_fee_dummy; + static htlc_extend_operation::fee_params_t htlc_extend_operation_fee_dummy; return htlc_extend_operation_fee_dummy; } }; @@ -198,19 +198,19 @@ namespace graphene { namespace protocol { void validate()const {} template - const typename Operation::fee_parameters_type& get()const + const typename Operation::fee_params_t& get()const { return fee_helper().cget(parameters); } template - typename Operation::fee_parameters_type& get() + typename Operation::fee_params_t& get() { return fee_helper().get(parameters); } template bool exists()const { - auto itr = parameters.find(typename Operation::fee_parameters_type()); + auto itr = parameters.find(typename Operation::fee_params_t()); return itr != parameters.end(); } diff --git a/libraries/protocol/include/graphene/protocol/htlc.hpp b/libraries/protocol/include/graphene/protocol/htlc.hpp index 606b2f383f..99da62d854 100644 --- a/libraries/protocol/include/graphene/protocol/htlc.hpp +++ b/libraries/protocol/include/graphene/protocol/htlc.hpp @@ -44,7 +44,7 @@ namespace graphene { namespace protocol { struct htlc_create_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; uint64_t fee_per_day = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; @@ -84,12 +84,12 @@ namespace graphene { namespace protocol { /**** * @brief calculates the fee to be paid for this operation */ - share_type calculate_fee(const fee_parameters_type& fee_params, uint32_t fee_per_kb)const; + share_type calculate_fee(const fee_params_t& fee_params, uint32_t fee_per_kb)const; }; struct htlc_redeem_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; uint64_t fee_per_kb = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; @@ -118,7 +118,7 @@ namespace graphene { namespace protocol { /**** * @brief calculates the fee to be paid for this operation */ - share_type calculate_fee(const fee_parameters_type& fee_params)const; + share_type calculate_fee(const fee_params_t& fee_params)const; }; /** @@ -126,7 +126,7 @@ namespace graphene { namespace protocol { */ struct htlc_redeemed_operation : public base_operation { - struct fee_parameters_type {}; + struct fee_params_t {}; htlc_redeemed_operation() {} htlc_redeemed_operation( htlc_id_type htlc_id, account_id_type from, account_id_type to, @@ -138,7 +138,7 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return to; } void validate()const { FC_ASSERT( !"virtual operation" ); } - share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + share_type calculate_fee(const fee_params_t& k)const { return 0; } htlc_id_type htlc_id; account_id_type from, to, redeemer; @@ -152,7 +152,7 @@ namespace graphene { namespace protocol { struct htlc_extend_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; uint64_t fee_per_day = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; @@ -181,12 +181,12 @@ namespace graphene { namespace protocol { /**** * @brief calculates the fee to be paid for this operation */ - share_type calculate_fee(const fee_parameters_type& fee_params)const; + share_type calculate_fee(const fee_params_t& fee_params)const; }; struct htlc_refund_operation : public base_operation { - struct fee_parameters_type {}; + struct fee_params_t {}; htlc_refund_operation(){} htlc_refund_operation( const htlc_id_type& htlc_id, @@ -199,7 +199,7 @@ namespace graphene { namespace protocol { 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; } + share_type calculate_fee(const fee_params_t& k)const { return 0; } asset fee; @@ -216,12 +216,12 @@ namespace graphene { namespace protocol { FC_REFLECT_TYPENAME( graphene::protocol::htlc_hash ) -FC_REFLECT( graphene::protocol::htlc_create_operation::fee_parameters_type, (fee) (fee_per_day) ) +FC_REFLECT( graphene::protocol::htlc_create_operation::fee_params_t, (fee) (fee_per_day) ) FC_REFLECT( graphene::protocol::htlc_create_operation::additional_options_type, (memo)) -FC_REFLECT( graphene::protocol::htlc_redeem_operation::fee_parameters_type, (fee) (fee_per_kb) ) -FC_REFLECT( graphene::protocol::htlc_redeemed_operation::fee_parameters_type, ) // VIRTUAL -FC_REFLECT( graphene::protocol::htlc_extend_operation::fee_parameters_type, (fee) (fee_per_day)) -FC_REFLECT( graphene::protocol::htlc_refund_operation::fee_parameters_type, ) // VIRTUAL +FC_REFLECT( graphene::protocol::htlc_redeem_operation::fee_params_t, (fee) (fee_per_kb) ) +FC_REFLECT( graphene::protocol::htlc_redeemed_operation::fee_params_t, ) // VIRTUAL +FC_REFLECT( graphene::protocol::htlc_extend_operation::fee_params_t, (fee) (fee_per_day)) +FC_REFLECT( graphene::protocol::htlc_refund_operation::fee_params_t, ) // VIRTUAL FC_REFLECT( graphene::protocol::htlc_create_operation, (fee)(from)(to)(amount)(preimage_hash)(preimage_size)(claim_period_seconds)(extensions)) @@ -232,10 +232,10 @@ FC_REFLECT( graphene::protocol::htlc_extend_operation, (fee)(htlc_id)(update_iss FC_REFLECT( graphene::protocol::htlc_refund_operation, (fee)(htlc_id)(to)(original_htlc_recipient)(htlc_amount)(htlc_preimage_hash)(htlc_preimage_size)) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation::additional_options_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_redeem_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_extend_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_redeem_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_extend_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_redeem_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_redeemed_operation ) diff --git a/libraries/protocol/include/graphene/protocol/liquidity_pool.hpp b/libraries/protocol/include/graphene/protocol/liquidity_pool.hpp index 712090de3c..ad11c33b55 100644 --- a/libraries/protocol/include/graphene/protocol/liquidity_pool.hpp +++ b/libraries/protocol/include/graphene/protocol/liquidity_pool.hpp @@ -33,7 +33,7 @@ namespace graphene { namespace protocol { */ struct liquidity_pool_create_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 50 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 50 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; ///< Operation fee account_id_type account; ///< The account who creates the liquidity pool @@ -55,7 +55,7 @@ namespace graphene { namespace protocol { */ struct liquidity_pool_delete_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 0; }; + struct fee_params_t { uint64_t fee = 0; }; asset fee; ///< Operation fee account_id_type account; ///< The account who owns the liquidity pool @@ -67,13 +67,33 @@ namespace graphene { namespace protocol { void validate()const; }; + /** + * @brief Update a liquidity pool + * @ingroup operations + */ + struct liquidity_pool_update_operation : public base_operation + { + struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; ///< Operation fee + account_id_type account; ///< The account who owns the liquidity pool + liquidity_pool_id_type pool; ///< ID of the liquidity pool + optional taker_fee_percent; ///< Taker fee percent + optional withdrawal_fee_percent; ///< Withdrawal fee percent + + extensions_type extensions; ///< Unused. Reserved for future use. + + account_id_type fee_payer()const { return account; } + void validate()const; + }; + /** * @brief Deposit to a liquidity pool * @ingroup operations */ struct liquidity_pool_deposit_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION / 10; }; + struct fee_params_t { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION / 10; }; asset fee; ///< Operation fee account_id_type account; ///< The account who deposits to the liquidity pool @@ -93,7 +113,7 @@ namespace graphene { namespace protocol { */ struct liquidity_pool_withdraw_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 5 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 5 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; ///< Operation fee account_id_type account; ///< The account who withdraws from the liquidity pool @@ -117,7 +137,7 @@ namespace graphene { namespace protocol { */ struct liquidity_pool_exchange_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; ///< Operation fee account_id_type account; ///< The account who exchanges with the liquidity pool @@ -133,17 +153,20 @@ namespace graphene { namespace protocol { } } // graphene::protocol -FC_REFLECT( graphene::protocol::liquidity_pool_create_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::liquidity_pool_delete_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::liquidity_pool_deposit_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::liquidity_pool_withdraw_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::liquidity_pool_exchange_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::liquidity_pool_create_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::liquidity_pool_delete_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::liquidity_pool_update_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::liquidity_pool_deposit_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::liquidity_pool_withdraw_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::liquidity_pool_exchange_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::liquidity_pool_create_operation, (fee)(account)(asset_a)(asset_b)(share_asset) (taker_fee_percent)(withdrawal_fee_percent)(extensions) ) FC_REFLECT( graphene::protocol::liquidity_pool_delete_operation, (fee)(account)(pool)(extensions) ) +FC_REFLECT( graphene::protocol::liquidity_pool_update_operation, + (fee)(account)(pool)(taker_fee_percent)(withdrawal_fee_percent)(extensions) ) FC_REFLECT( graphene::protocol::liquidity_pool_deposit_operation, (fee)(account)(pool)(amount_a)(amount_b)(extensions) ) FC_REFLECT( graphene::protocol::liquidity_pool_withdraw_operation, @@ -151,14 +174,16 @@ FC_REFLECT( graphene::protocol::liquidity_pool_withdraw_operation, FC_REFLECT( graphene::protocol::liquidity_pool_exchange_operation, (fee)(account)(pool)(amount_to_sell)(min_to_receive)(extensions) ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_create_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_delete_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_deposit_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_withdraw_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_exchange_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_create_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_delete_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_update_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_deposit_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_withdraw_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_exchange_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_create_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_delete_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_update_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_deposit_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_withdraw_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_exchange_operation ) diff --git a/libraries/protocol/include/graphene/protocol/market.hpp b/libraries/protocol/include/graphene/protocol/market.hpp index b5dfd413aa..b84e00219e 100644 --- a/libraries/protocol/include/graphene/protocol/market.hpp +++ b/libraries/protocol/include/graphene/protocol/market.hpp @@ -27,6 +27,30 @@ namespace graphene { namespace protocol { + /** + * Creates a take profit limit order + */ + struct create_take_profit_order_action + { + /// Asset ID that will be used to pay operation fee for placing the take profit order + asset_id_type fee_asset_id; + /// A percentage indicating how far the price of the take profit order differs from the original order + uint16_t spread_percent = 0; + /// A percentage indicating how much amount to sell in the take profit order + uint16_t size_percent = GRAPHENE_100_PERCENT; + /// How long the take profit order to be kept on the market + uint32_t expiration_seconds = 0; + /// Whether to create another take profit order for this take profit order if this take profit order is matched + bool repeat = false; + + extensions_type extensions; ///< Unused. Reserved for future use. + + void validate()const; + }; + + /// Automatic actions for limit orders + using limit_order_auto_action = static_variant< create_take_profit_order_action >; + /** * @class limit_order_create_operation * @brief instructs the blockchain to attempt to sell one asset for another @@ -47,7 +71,18 @@ namespace graphene { namespace protocol { */ struct limit_order_create_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 5 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + /** + * Options to be used in @ref limit_order_create_operation + * + * @note this struct can be expanded by adding more options in the end. + */ + struct options_type + { + /// Automatic actions when the limit order is filled or partially filled + optional< vector< limit_order_auto_action > > on_fill; + }; + + struct fee_params_t { uint64_t fee = 5 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; account_id_type seller; @@ -60,6 +95,8 @@ namespace graphene { namespace protocol { /// If this flag is set the entire order must be filled or the operation is rejected bool fill_or_kill = false; + + using extensions_type = extension; // note: will be jsonified to {...} but not [...] extensions_type extensions; pair get_market()const @@ -73,6 +110,31 @@ namespace graphene { namespace protocol { price get_price()const { return amount_to_sell / min_to_receive; } }; + /** + * @ingroup operations + * Used to update an existing limit order. + */ + struct limit_order_update_operation : public base_operation + { + struct fee_params_t { + uint64_t fee = ( GRAPHENE_BLOCKCHAIN_PRECISION * 3 ) / 8; + }; + + asset fee; + account_id_type seller; + limit_order_id_type order; + optional new_price; + optional delta_amount_to_sell; + optional new_expiration; + /// Automatic actions when the limit order is filled or partially filled + optional< vector< limit_order_auto_action > > on_fill; + + extensions_type extensions; + + account_id_type fee_payer() const { return seller; } + void validate() const override; + }; + /** * @ingroup operations * Used to cancel an existing limit order. Both fee_pay_account and the @@ -82,7 +144,7 @@ namespace graphene { namespace protocol { */ struct limit_order_cancel_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 0; }; + struct fee_params_t { uint64_t fee = 0; }; asset fee; limit_order_id_type order; @@ -120,14 +182,14 @@ namespace graphene { namespace protocol { }; /** this is slightly more expensive than limit orders, this pricing impacts prediction markets */ - struct fee_parameters_type { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; account_id_type funding_account; ///< pays fee, collateral, and cover asset delta_collateral; ///< the amount of collateral to add to the margin position asset delta_debt; ///< the amount of the debt to be paid off, may be negative to issue new debt - typedef extension extensions_type; // note: this will be jsonified to {...} but no longer [...] + using extensions_type = extension; // note: this will be jsonified to {...} but no longer [...] extensions_type extensions; account_id_type fee_payer()const { return funding_account; } @@ -143,7 +205,7 @@ namespace graphene { namespace protocol { */ struct fill_order_operation : public base_operation { - struct fee_parameters_type {}; + struct fee_params_t {}; fill_order_operation(){} fill_order_operation( object_id_type o, account_id_type a, asset p, asset r, asset f, price fp, bool m ) @@ -167,7 +229,7 @@ namespace graphene { namespace protocol { 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; } + share_type calculate_fee(const fee_params_t&)const { return 0; } }; /** @@ -179,7 +241,7 @@ namespace graphene { namespace protocol { struct bid_collateral_operation : public base_operation { /** should be equivalent to call_order_update fee */ - struct fee_parameters_type { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; account_id_type bidder; ///< pays fee and additional collateral @@ -199,7 +261,7 @@ namespace graphene { namespace protocol { */ struct execute_bid_operation : public base_operation { - struct fee_parameters_type {}; + struct fee_params_t {}; execute_bid_operation(){} execute_bid_operation( account_id_type a, asset d, asset c ) @@ -214,21 +276,28 @@ namespace graphene { namespace protocol { 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; } + share_type calculate_fee(const fee_params_t&)const { return 0; } }; } } // graphene::protocol -FC_REFLECT( graphene::protocol::limit_order_create_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::limit_order_cancel_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::call_order_update_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::bid_collateral_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::fill_order_operation::fee_parameters_type, ) // VIRTUAL -FC_REFLECT( graphene::protocol::execute_bid_operation::fee_parameters_type, ) // VIRTUAL +FC_REFLECT( graphene::protocol::create_take_profit_order_action, + (fee_asset_id)(spread_percent)(size_percent)(expiration_seconds)(repeat)(extensions) ) + +FC_REFLECT( graphene::protocol::limit_order_create_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::limit_order_update_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::limit_order_cancel_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::call_order_update_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::bid_collateral_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::fill_order_operation::fee_params_t, ) // VIRTUAL +FC_REFLECT( graphene::protocol::execute_bid_operation::fee_params_t, ) // VIRTUAL +FC_REFLECT( graphene::protocol::limit_order_create_operation::options_type, (on_fill) ) FC_REFLECT( graphene::protocol::call_order_update_operation::options_type, (target_collateral_ratio) ) FC_REFLECT( graphene::protocol::limit_order_create_operation, (fee)(seller)(amount_to_sell)(min_to_receive)(expiration)(fill_or_kill)(extensions)) +FC_REFLECT( graphene::protocol::limit_order_update_operation, + (fee)(seller)(order)(new_price)(delta_amount_to_sell)(new_expiration)(on_fill)(extensions)) FC_REFLECT( graphene::protocol::limit_order_cancel_operation, (fee)(fee_paying_account)(order)(extensions) ) FC_REFLECT( graphene::protocol::call_order_update_operation, @@ -240,12 +309,19 @@ FC_REFLECT( graphene::protocol::bid_collateral_operation, FC_REFLECT( graphene::protocol::execute_bid_operation, (fee)(bidder)(debt)(collateral) ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::create_take_profit_order_action) + +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_create_operation::options_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation::options_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_create_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_cancel_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::bid_collateral_operation::fee_parameters_type ) + +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_create_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_update_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_cancel_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::bid_collateral_operation::fee_params_t ) + GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_create_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_update_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_cancel_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::bid_collateral_operation ) diff --git a/libraries/protocol/include/graphene/protocol/object_id.hpp b/libraries/protocol/include/graphene/protocol/object_id.hpp index 2fda3cf742..f64e737ec0 100644 --- a/libraries/protocol/include/graphene/protocol/object_id.hpp +++ b/libraries/protocol/include/graphene/protocol/object_id.hpp @@ -240,12 +240,14 @@ struct member_name, 0> { static constexpr const cha FC_ASSERT( type_id <= graphene::db::object_id_type::one_byte_mask, "type overflow"); auto instance = fc::to_uint64(s.substr( second_dot+1 )); vo.reset( static_cast(space_id), static_cast(type_id), instance ); - } FC_CAPTURE_AND_RETHROW( (var) ) } + } FC_CAPTURE_AND_RETHROW( (var) ) } // GCOVR_EXCL_LINE + template void to_variant( const graphene::db::object_id& var, fc::variant& vo, uint32_t max_depth = 1 ) { vo = std::string( var ); } + template void from_variant( const fc::variant& var, graphene::db::object_id& vo, uint32_t max_depth = 1 ) { try { @@ -262,7 +264,7 @@ struct member_name, 0> { static constexpr const cha ("TypeID",TypeID)("SpaceID",SpaceID)("var",var) ); graphene::db::object_id tmp { fc::to_uint64(s.substr( second_dot+1 )) }; vo = tmp; - } FC_CAPTURE_AND_RETHROW( (var) ) } + } FC_CAPTURE_AND_RETHROW( (var) ) } // GCOVR_EXCL_LINE } // namespace fc diff --git a/libraries/protocol/include/graphene/protocol/operations.hpp b/libraries/protocol/include/graphene/protocol/operations.hpp index 0b32f7e5da..aea9903a9f 100644 --- a/libraries/protocol/include/graphene/protocol/operations.hpp +++ b/libraries/protocol/include/graphene/protocol/operations.hpp @@ -127,11 +127,12 @@ namespace graphene { namespace protocol { /* 71 */ credit_offer_update_operation, /* 72 */ credit_offer_accept_operation, /* 73 */ credit_deal_repay_operation, - /* 74 */ credit_deal_expired_operation // VIRTUAL + /* 74 */ credit_deal_expired_operation, // VIRTUAL + /* 75 */ liquidity_pool_update_operation, + /* 76 */ credit_deal_update_operation, + /* 77 */ limit_order_update_operation >; - /// @} // operations group - /** * Appends required authorites to the result vector. The authorities appended are not the * same as those returned by get_required_auth diff --git a/libraries/protocol/include/graphene/protocol/proposal.hpp b/libraries/protocol/include/graphene/protocol/proposal.hpp index a1a7b27e1f..6d3ce173a2 100644 --- a/libraries/protocol/include/graphene/protocol/proposal.hpp +++ b/libraries/protocol/include/graphene/protocol/proposal.hpp @@ -69,7 +69,7 @@ namespace graphene { namespace protocol { */ struct proposal_create_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; uint32_t price_per_kbyte = 10; }; @@ -95,7 +95,7 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return fee_paying_account; } void validate()const; - share_type calculate_fee(const fee_parameters_type& k)const; + share_type calculate_fee(const fee_params_t& k)const; }; /** @@ -118,7 +118,7 @@ namespace graphene { namespace protocol { */ struct proposal_update_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; uint32_t price_per_kbyte = 10; }; @@ -136,7 +136,7 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return fee_paying_account; } void validate()const; - share_type calculate_fee(const fee_parameters_type& k)const; + share_type calculate_fee(const fee_params_t& k)const; void get_required_authorities( vector& )const; void get_required_active_authorities( flat_set& )const; void get_required_owner_authorities( flat_set& )const; @@ -155,7 +155,7 @@ namespace graphene { namespace protocol { */ struct proposal_delete_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; account_id_type fee_paying_account; bool using_owner_authority = false; @@ -170,9 +170,9 @@ namespace graphene { namespace protocol { }} // graphene::protocol -FC_REFLECT( graphene::protocol::proposal_create_operation::fee_parameters_type, (fee)(price_per_kbyte) ) -FC_REFLECT( graphene::protocol::proposal_update_operation::fee_parameters_type, (fee)(price_per_kbyte) ) -FC_REFLECT( graphene::protocol::proposal_delete_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::proposal_create_operation::fee_params_t, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::protocol::proposal_update_operation::fee_params_t, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::protocol::proposal_delete_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::proposal_create_operation, (fee)(fee_paying_account)(expiration_time) (proposed_ops)(review_period_seconds)(extensions) ) @@ -181,9 +181,9 @@ FC_REFLECT( graphene::protocol::proposal_update_operation, (fee)(fee_paying_acco (key_approvals_to_add)(key_approvals_to_remove)(extensions) ) FC_REFLECT( graphene::protocol::proposal_delete_operation, (fee)(fee_paying_account)(using_owner_authority)(proposal)(extensions) ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_create_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_update_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_delete_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_create_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_update_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_delete_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_create_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_update_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::proposal_delete_operation ) diff --git a/libraries/protocol/include/graphene/protocol/samet_fund.hpp b/libraries/protocol/include/graphene/protocol/samet_fund.hpp index e7c4bcfe1f..95dfac6c3f 100644 --- a/libraries/protocol/include/graphene/protocol/samet_fund.hpp +++ b/libraries/protocol/include/graphene/protocol/samet_fund.hpp @@ -35,7 +35,7 @@ namespace graphene { namespace protocol { */ struct samet_fund_create_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; ///< Operation fee account_id_type owner_account; ///< Owner of the fund @@ -55,7 +55,7 @@ namespace graphene { namespace protocol { */ struct samet_fund_delete_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 0; }; + struct fee_params_t { uint64_t fee = 0; }; asset fee; ///< Operation fee account_id_type owner_account; ///< The account who owns the SameT Fund object @@ -73,7 +73,7 @@ namespace graphene { namespace protocol { */ struct samet_fund_update_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; ///< Operation fee account_id_type owner_account; ///< Owner of the fund @@ -93,7 +93,7 @@ namespace graphene { namespace protocol { */ struct samet_fund_borrow_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; ///< Operation fee account_id_type borrower; ///< The account who borrows from the fund @@ -112,7 +112,7 @@ namespace graphene { namespace protocol { */ struct samet_fund_repay_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; ///< Operation fee account_id_type account; ///< The account who repays to the SameT Fund @@ -128,11 +128,11 @@ namespace graphene { namespace protocol { } } // graphene::protocol -FC_REFLECT( graphene::protocol::samet_fund_create_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::samet_fund_delete_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::samet_fund_update_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::samet_fund_borrow_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::samet_fund_repay_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::samet_fund_create_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::samet_fund_delete_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::samet_fund_update_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::samet_fund_borrow_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::samet_fund_repay_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::samet_fund_create_operation, (fee)(owner_account)(asset_type)(balance)(fee_rate)(extensions) ) @@ -145,11 +145,11 @@ FC_REFLECT( graphene::protocol::samet_fund_borrow_operation, FC_REFLECT( graphene::protocol::samet_fund_repay_operation, (fee)(account)(fund_id)(repay_amount)(fund_fee)(extensions) ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_create_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_delete_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_update_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_borrow_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_repay_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_create_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_delete_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_update_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_borrow_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_repay_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_create_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_delete_operation ) diff --git a/libraries/protocol/include/graphene/protocol/ticket.hpp b/libraries/protocol/include/graphene/protocol/ticket.hpp index a4b761febc..774689014c 100644 --- a/libraries/protocol/include/graphene/protocol/ticket.hpp +++ b/libraries/protocol/include/graphene/protocol/ticket.hpp @@ -46,7 +46,7 @@ namespace graphene { namespace protocol { */ struct ticket_create_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 50 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 50 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; ///< Operation fee account_id_type account; ///< The account who creates the ticket @@ -65,7 +65,7 @@ namespace graphene { namespace protocol { */ struct ticket_update_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 50 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 50 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; ///< Operation fee ticket_id_type ticket; ///< The ticket to update @@ -84,16 +84,16 @@ namespace graphene { namespace protocol { FC_REFLECT_ENUM( graphene::protocol::ticket_type, (liquid)(lock_180_days)(lock_360_days)(lock_720_days)(lock_forever)(TICKET_TYPE_COUNT) ) -FC_REFLECT( graphene::protocol::ticket_create_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::ticket_update_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::ticket_create_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::ticket_update_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::ticket_create_operation, (fee)(account)(target_type)(amount)(extensions) ) FC_REFLECT( graphene::protocol::ticket_update_operation, (fee)(ticket)(account)(target_type)(amount_for_new_target)(extensions) ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::ticket_create_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::ticket_update_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::ticket_create_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::ticket_update_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::ticket_create_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::ticket_update_operation ) diff --git a/libraries/protocol/include/graphene/protocol/transfer.hpp b/libraries/protocol/include/graphene/protocol/transfer.hpp index 2125ed4eb6..7117cd0610 100644 --- a/libraries/protocol/include/graphene/protocol/transfer.hpp +++ b/libraries/protocol/include/graphene/protocol/transfer.hpp @@ -44,7 +44,7 @@ namespace graphene { namespace protocol { */ struct transfer_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; uint32_t price_per_kbyte = 10 * GRAPHENE_BLOCKCHAIN_PRECISION; /// only required for large memos. }; @@ -63,7 +63,7 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return from; } void validate()const; - share_type calculate_fee(const fee_parameters_type& k)const; + share_type calculate_fee(const fee_params_t& k)const; }; /** @@ -76,7 +76,7 @@ namespace graphene { namespace protocol { */ struct override_transfer_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; uint32_t price_per_kbyte = 10; /// only required for large memos. }; @@ -96,18 +96,18 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return issuer; } void validate()const; - share_type calculate_fee(const fee_parameters_type& k)const; + share_type calculate_fee(const fee_params_t& k)const; }; }} // graphene::protocol -FC_REFLECT( graphene::protocol::transfer_operation::fee_parameters_type, (fee)(price_per_kbyte) ) -FC_REFLECT( graphene::protocol::override_transfer_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::protocol::transfer_operation::fee_params_t, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::protocol::override_transfer_operation::fee_params_t, (fee)(price_per_kbyte) ) FC_REFLECT( graphene::protocol::override_transfer_operation, (fee)(issuer)(from)(to)(amount)(memo)(extensions) ) FC_REFLECT( graphene::protocol::transfer_operation, (fee)(from)(to)(amount)(memo)(extensions) ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::override_transfer_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::override_transfer_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::transfer_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::override_transfer_operation ) diff --git a/libraries/protocol/include/graphene/protocol/vesting.hpp b/libraries/protocol/include/graphene/protocol/vesting.hpp index a733506d1d..f632569d29 100644 --- a/libraries/protocol/include/graphene/protocol/vesting.hpp +++ b/libraries/protocol/include/graphene/protocol/vesting.hpp @@ -73,7 +73,7 @@ namespace graphene { namespace protocol { */ struct vesting_balance_create_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; account_id_type creator; ///< Who provides funds initially @@ -100,7 +100,7 @@ namespace graphene { namespace protocol { */ struct vesting_balance_withdraw_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 20*GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 20*GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; vesting_balance_id_type vesting_balance; @@ -118,8 +118,8 @@ namespace graphene { namespace protocol { } } // graphene::protocol -FC_REFLECT( graphene::protocol::vesting_balance_create_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::vesting_balance_withdraw_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::vesting_balance_create_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::vesting_balance_withdraw_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::vesting_balance_create_operation, (fee)(creator)(owner)(amount)(policy) ) FC_REFLECT( graphene::protocol::vesting_balance_withdraw_operation, (fee)(vesting_balance)(owner)(amount) ) @@ -129,7 +129,7 @@ FC_REFLECT(graphene::protocol::cdd_vesting_policy_initializer, (start_claim)(ves FC_REFLECT_EMPTY( graphene::protocol::instant_vesting_policy_initializer ) FC_REFLECT_TYPENAME( graphene::protocol::vesting_policy_initializer ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::vesting_balance_create_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::vesting_balance_withdraw_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::vesting_balance_create_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::vesting_balance_withdraw_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::vesting_balance_create_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::vesting_balance_withdraw_operation ) diff --git a/libraries/protocol/include/graphene/protocol/withdraw_permission.hpp b/libraries/protocol/include/graphene/protocol/withdraw_permission.hpp index a5d01b79ef..9398681c29 100644 --- a/libraries/protocol/include/graphene/protocol/withdraw_permission.hpp +++ b/libraries/protocol/include/graphene/protocol/withdraw_permission.hpp @@ -49,7 +49,7 @@ namespace graphene { namespace protocol { */ struct withdraw_permission_create_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; /// The account authorizing withdrawals from its balances @@ -82,7 +82,7 @@ namespace graphene { namespace protocol { */ struct withdraw_permission_update_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; /// This account pays the fee. Must match permission_to_update->withdraw_from_account @@ -119,7 +119,7 @@ namespace graphene { namespace protocol { */ struct withdraw_permission_claim_operation : public base_operation { - struct fee_parameters_type { + struct fee_params_t { uint64_t fee = 20*GRAPHENE_BLOCKCHAIN_PRECISION; uint32_t price_per_kbyte = 10; }; @@ -139,7 +139,7 @@ namespace graphene { namespace protocol { account_id_type fee_payer()const { return withdraw_to_account; } void validate()const; - share_type calculate_fee(const fee_parameters_type& k)const; + share_type calculate_fee(const fee_params_t& k)const; }; /** @@ -152,7 +152,7 @@ namespace graphene { namespace protocol { */ struct withdraw_permission_delete_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 0; }; + struct fee_params_t { uint64_t fee = 0; }; asset fee; /// Must match withdrawal_permission->withdraw_from_account. This account pays the fee. @@ -168,10 +168,10 @@ namespace graphene { namespace protocol { } } // graphene::protocol -FC_REFLECT( graphene::protocol::withdraw_permission_create_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::withdraw_permission_update_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::protocol::withdraw_permission_claim_operation::fee_parameters_type, (fee)(price_per_kbyte) ) -FC_REFLECT( graphene::protocol::withdraw_permission_delete_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::withdraw_permission_create_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::withdraw_permission_update_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::withdraw_permission_claim_operation::fee_params_t, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::protocol::withdraw_permission_delete_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::withdraw_permission_create_operation, (fee)(withdraw_from_account)(authorized_account) @@ -186,10 +186,10 @@ FC_REFLECT( graphene::protocol::withdraw_permission_delete_operation, (fee)(withdraw_from_account)(authorized_account) (withdrawal_permission) ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_create_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_update_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_claim_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_delete_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_create_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_update_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_claim_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_delete_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_create_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_update_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_claim_operation ) diff --git a/libraries/protocol/include/graphene/protocol/witness.hpp b/libraries/protocol/include/graphene/protocol/witness.hpp index 89efe54f70..5c176d29fd 100644 --- a/libraries/protocol/include/graphene/protocol/witness.hpp +++ b/libraries/protocol/include/graphene/protocol/witness.hpp @@ -36,7 +36,7 @@ namespace graphene { namespace protocol { */ struct witness_create_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 5000 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 5000 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; /// The account which owns the witness. This account pays the fee for this operation. @@ -54,7 +54,7 @@ namespace graphene { namespace protocol { */ struct witness_update_operation : public base_operation { - struct fee_parameters_type + struct fee_params_t { share_type fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; }; @@ -77,13 +77,13 @@ namespace graphene { namespace protocol { } } // graphene::protocol -FC_REFLECT( graphene::protocol::witness_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::witness_create_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::witness_create_operation, (fee)(witness_account)(url)(block_signing_key) ) -FC_REFLECT( graphene::protocol::witness_update_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::witness_update_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::witness_update_operation, (fee)(witness)(witness_account)(new_url)(new_signing_key) ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::witness_create_operation::fee_parameters_type ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::witness_update_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::witness_create_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::witness_update_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::witness_create_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::witness_update_operation ) diff --git a/libraries/protocol/include/graphene/protocol/worker.hpp b/libraries/protocol/include/graphene/protocol/worker.hpp index 1dfc472d77..ee1244a8f4 100644 --- a/libraries/protocol/include/graphene/protocol/worker.hpp +++ b/libraries/protocol/include/graphene/protocol/worker.hpp @@ -78,7 +78,7 @@ namespace graphene { namespace protocol { */ struct worker_create_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 5000*GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_params_t { uint64_t fee = 5000*GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; account_id_type owner; @@ -102,9 +102,9 @@ FC_REFLECT( graphene::protocol::burn_worker_initializer, ) FC_REFLECT( graphene::protocol::refund_worker_initializer, ) FC_REFLECT_TYPENAME( graphene::protocol::worker_initializer ) -FC_REFLECT( graphene::protocol::worker_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::worker_create_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::worker_create_operation, (fee)(owner)(work_begin_date)(work_end_date)(daily_pay)(name)(url)(initializer) ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::worker_create_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::worker_create_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::worker_create_operation ) diff --git a/libraries/protocol/liquidity_pool.cpp b/libraries/protocol/liquidity_pool.cpp index 956273609d..36574ece5c 100644 --- a/libraries/protocol/liquidity_pool.cpp +++ b/libraries/protocol/liquidity_pool.cpp @@ -42,6 +42,16 @@ void liquidity_pool_delete_operation::validate()const FC_ASSERT( fee.amount >= 0, "Fee should not be negative" ); } +void liquidity_pool_update_operation::validate()const +{ + FC_ASSERT( fee.amount >= 0, "Fee should not be negative" ); + FC_ASSERT( taker_fee_percent.valid() || withdrawal_fee_percent.valid(), "Should update something" ); + if( taker_fee_percent.valid() ) + FC_ASSERT( *taker_fee_percent <= GRAPHENE_100_PERCENT, "Taker fee percent should not exceed 100%" ); + if( withdrawal_fee_percent.valid() ) + FC_ASSERT( 0 == *withdrawal_fee_percent, "Withdrawal fee percent can only be updated to zero" ); +} + void liquidity_pool_deposit_operation::validate()const { FC_ASSERT( fee.amount >= 0, "Fee should not be negative" ); @@ -67,14 +77,16 @@ void liquidity_pool_exchange_operation::validate()const } } // graphene::protocol -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_create_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_delete_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_deposit_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_withdraw_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_exchange_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_create_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_delete_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_update_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_deposit_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_withdraw_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_exchange_operation::fee_params_t ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_create_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_delete_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_update_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_deposit_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_withdraw_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::liquidity_pool_exchange_operation ) diff --git a/libraries/protocol/market.cpp b/libraries/protocol/market.cpp index 8e8fc721e2..63bbdfb17f 100644 --- a/libraries/protocol/market.cpp +++ b/libraries/protocol/market.cpp @@ -27,14 +27,60 @@ namespace graphene { namespace protocol { +void create_take_profit_order_action::validate() const +{ + FC_ASSERT( spread_percent > 0, "The spread percentage must be positive" ); + FC_ASSERT( size_percent > 0, "The size percentage must be positive" ); + FC_ASSERT( size_percent <= GRAPHENE_100_PERCENT, "The size percentage must not exceed 100%" ); + FC_ASSERT( expiration_seconds > 0, "The expiration seconds must be positive" ); +} + +struct lo_action_validate_visitor +{ + using result_type = void; + + template + result_type operator()( const ActionType& action )const + { + action.validate(); + } +}; + void limit_order_create_operation::validate()const { FC_ASSERT( amount_to_sell.asset_id != min_to_receive.asset_id ); FC_ASSERT( fee.amount >= 0 ); FC_ASSERT( amount_to_sell.amount > 0 ); FC_ASSERT( min_to_receive.amount > 0 ); + + if( extensions.value.on_fill.valid() ) + { + // Note: an empty on_fill action list is allowed + for( const auto& action : *extensions.value.on_fill ) + action.visit( lo_action_validate_visitor() ); + } + } +void limit_order_update_operation::validate() const +{ try { + FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); + FC_ASSERT(new_price || delta_amount_to_sell || new_expiration || on_fill, + "Cannot update limit order if nothing is specified to update"); + if (new_price) + new_price->validate(); + if (delta_amount_to_sell) + FC_ASSERT(delta_amount_to_sell->amount != 0, "Cannot change limit order amount by zero"); + + if( on_fill.valid() ) + { + // Note: an empty on_fill action list is allowed + for( const auto& action : *on_fill ) + action.visit( lo_action_validate_visitor() ); + } + +} FC_CAPTURE_AND_RETHROW((*this)) } // GCOVR_EXCL_LINE + void limit_order_cancel_operation::validate()const { FC_ASSERT( fee.amount >= 0 ); @@ -48,22 +94,29 @@ void call_order_update_operation::validate()const // note: no validation is needed for extensions so far: the only attribute inside is target_collateral_ratio -} FC_CAPTURE_AND_RETHROW((*this)) } +} FC_CAPTURE_AND_RETHROW((*this)) } // GCOVR_EXCL_LINE void bid_collateral_operation::validate()const { try { FC_ASSERT( fee.amount >= 0 ); FC_ASSERT( debt_covered.amount == 0 || (debt_covered.amount > 0 && additional_collateral.amount > 0) ); -} FC_CAPTURE_AND_RETHROW((*this)) } +} FC_CAPTURE_AND_RETHROW((*this)) } // GCOVR_EXCL_LINE } } // graphene::protocol +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::create_take_profit_order_action ) + +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_create_operation::options_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation::options_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_create_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_cancel_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::bid_collateral_operation::fee_parameters_type ) + +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_create_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_update_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_cancel_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::bid_collateral_operation::fee_params_t ) + GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_create_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_update_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_cancel_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::bid_collateral_operation ) diff --git a/libraries/protocol/proposal.cpp b/libraries/protocol/proposal.cpp index 22d66d44f1..55432cdd5c 100644 --- a/libraries/protocol/proposal.cpp +++ b/libraries/protocol/proposal.cpp @@ -43,7 +43,7 @@ void proposal_create_operation::validate() const for( const auto& op : proposed_ops ) operation_validate( op.op ); } -share_type proposal_create_operation::calculate_fee(const fee_parameters_type& k) const +share_type proposal_create_operation::calculate_fee(const fee_params_t& k) const { return k.fee + calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte ); } @@ -76,7 +76,7 @@ void proposal_delete_operation::validate() const FC_ASSERT( fee.amount >= 0 ); } -share_type proposal_update_operation::calculate_fee(const fee_parameters_type& k) const +share_type proposal_update_operation::calculate_fee(const fee_params_t& k) const { return k.fee + calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte ); } @@ -108,9 +108,9 @@ void proposal_update_operation::get_required_owner_authorities( flat_set= 0 ); } -share_type withdraw_permission_claim_operation::calculate_fee(const fee_parameters_type& k)const +share_type withdraw_permission_claim_operation::calculate_fee(const fee_params_t& k)const { share_type core_fee_required = k.fee; if( memo ) @@ -69,10 +69,10 @@ void withdraw_permission_delete_operation::validate() const } } // graphene::protocol -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_create_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_update_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_claim_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_delete_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_create_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_update_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_claim_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_delete_operation::fee_params_t ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_create_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_update_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::withdraw_permission_claim_operation ) diff --git a/libraries/protocol/witness.cpp b/libraries/protocol/witness.cpp index 5085156fe7..c8e2c9ee91 100644 --- a/libraries/protocol/witness.cpp +++ b/libraries/protocol/witness.cpp @@ -42,7 +42,7 @@ void witness_update_operation::validate() const } } // graphene::protocol -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::witness_create_operation::fee_parameters_type ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::witness_update_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::witness_create_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::witness_update_operation::fee_params_t ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::witness_create_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::witness_update_operation ) diff --git a/libraries/protocol/worker.cpp b/libraries/protocol/worker.cpp index 9b6a5d475c..195639b7e1 100644 --- a/libraries/protocol/worker.cpp +++ b/libraries/protocol/worker.cpp @@ -39,5 +39,5 @@ void worker_create_operation::validate() const } } -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::worker_create_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::worker_create_operation::fee_params_t ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::worker_create_operation ) diff --git a/libraries/utilities/elasticsearch.cpp b/libraries/utilities/elasticsearch.cpp index 7393a701ed..87aa7c6c8f 100644 --- a/libraries/utilities/elasticsearch.cpp +++ b/libraries/utilities/elasticsearch.cpp @@ -211,13 +211,14 @@ std::string es_client::get_version() const fc::variant content = fc::json::from_string( response.content ); return content["version"]["number"].as_string(); -} FC_CAPTURE_AND_RETHROW() } +} FC_CAPTURE_LOG_AND_RETHROW( (base_url) ) } // GCOVR_EXCL_LINE void es_client::check_version_7_or_above( bool& result ) const noexcept { static const int64_t version_7 = 7; try { const auto es_version = get_version(); + ilog( "ES version detected: ${v}", ("v", es_version) ); auto dot_pos = es_version.find('.'); result = ( std::stoi(es_version.substr(0,dot_pos)) >= version_7 ); } @@ -271,28 +272,41 @@ fc::variant es_data_adaptor::adapt( const fc::variant_object& op, uint16_t max_d fc::mutable_variant_object o(op); - // Note: these fields are maps, but were stored in ES as flattened arrays - static const std::unordered_set flattened_fields = { "account_auths", "address_auths", "key_auths" }; + // Note: + // These fields are maps, they are stored redundantly in ES, + // one instance is a nested string array using the original field names (for backward compatibility, although + // ES queries return results in JSON format a little differently than node APIs), + // and a new instance is an object array with "_object" suffix added to the field name. + static const std::unordered_set to_string_array_fields = { "account_auths", "address_auths", + "key_auths" }; // Note: - // object arrays listed in this map are stored redundantly in ES, with one instance as a nested object and - // the other as a string for backward compatibility, - // object arrays not listed in this map are stored as nested objects only. + // These fields are stored redundantly in ES, + // one instance is a string using the original field names (originally for backward compatibility, + // but new fields are added here as well), + // and a new instance is a nested object or nested object array with "_object" suffix added to the field name. + // + // Why do we add new fields here? + // Because we want to keep the JSON format made by node (stored in ES as a string), and store the object format + // at the same time for more flexible query. + // + // Object arrays not listed in this map (if any) are stored as nested objects only. static const std::unordered_map to_string_fields = { { "parameters", data_type::array_type }, // in committee proposals, current_fees.parameters { "op", data_type::static_variant_type }, // proposal_create_op.proposed_ops[*].op - { "proposed_ops", data_type::array_type }, + { "proposed_ops", data_type::array_type }, // proposal_create_op.proposed_ops { "operations", data_type::array_type }, // proposal_object.operations - { "initializer", data_type::static_variant_type }, - { "policy", data_type::static_variant_type }, - { "predicates", data_type::array_type }, - { "active_special_authority", data_type::static_variant_type }, - { "owner_special_authority", data_type::static_variant_type }, - { "htlc_preimage_hash", data_type::static_variant_type }, + { "initializer", data_type::static_variant_type }, // for workers + { "policy", data_type::static_variant_type }, // for vesting balances + { "predicates", data_type::array_type }, // for assert_operation + { "active_special_authority", data_type::static_variant_type }, // for accounts + { "owner_special_authority", data_type::static_variant_type }, // for accounts + { "htlc_preimage_hash", data_type::static_variant_type }, // for HTLCs { "argument", data_type::static_variant_type }, // for custom authority, restriction.argument { "feeds", data_type::map_type }, // asset_bitasset_data_object.feeds - { "acceptable_collateral", data_type::map_type }, - { "acceptable_borrowers", data_type::map_type } + { "acceptable_collateral", data_type::map_type }, // for credit offers + { "acceptable_borrowers", data_type::map_type }, // for credit offers + { "on_fill", data_type::array_type } // for limit orders }; std::vector> original_arrays; std::vector keys_to_rename; @@ -320,7 +334,7 @@ fc::variant es_data_adaptor::adapt( const fc::variant_object& op, uint16_t max_d original_arrays.emplace_back( name, array ); element = fc::json::to_string(element); } - else if( flattened_fields.find(name) != flattened_fields.end() ) + else if( to_string_array_fields.find(name) != to_string_array_fields.end() ) { // make a backup (only if depth is sufficient) and adapt the original if( max_depth > 1 ) diff --git a/libraries/wallet/CMakeLists.txt b/libraries/wallet/CMakeLists.txt index 09a72fc118..cd5bfef7f0 100644 --- a/libraries/wallet/CMakeLists.txt +++ b/libraries/wallet/CMakeLists.txt @@ -50,8 +50,18 @@ target_link_libraries( graphene_wallet PRIVATE graphene_app graphene_chain graph ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) target_include_directories( graphene_db PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) +set( GRAPHENE_WALLET_BIG_FILES + wallet.cpp + wallet_api_impl.cpp + wallet_voting.cpp + ) + if(MSVC) - set_source_files_properties( wallet.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) + set_source_files_properties( ${GRAPHENE_WALLET_BIG_FILES} PROPERTIES COMPILE_FLAGS "/bigobj" ) +else( MSVC ) + if( MINGW ) + set_source_files_properties( ${GRAPHENE_WALLET_BIG_FILES} PROPERTIES COMPILE_FLAGS -Wa,-mbig-obj ) + endif( MINGW ) endif(MSVC) install( TARGETS diff --git a/libraries/wallet/generate_api_documentation.pl b/libraries/wallet/generate_api_documentation.pl index a515582396..fd985b2471 100755 --- a/libraries/wallet/generate_api_documentation.pl +++ b/libraries/wallet/generate_api_documentation.pl @@ -16,7 +16,7 @@ #include namespace graphene { namespace wallet { - namespace detail + namespace detail { struct api_method_name_collector_visitor { @@ -29,7 +29,7 @@ } }; } - + api_documentation::api_documentation() { END @@ -110,17 +110,26 @@ sub formatDocComment for (my $i = 0; $i < @{$doc}; ++$i) { - if ($doc->[$i] eq 'params') + if (($doc->[$i] eq 'params') # doxygen version 1.8.11 (Ubuntu 16.04) or 1.8.13 (Ubuntu 18.04) + or ($doc->[$i]->{params})) # doxygen version 1.8.17 (Ubuntu 20.04) { $paramDocs .= "Parameters:\n"; - @parametersList = @{$doc->[$i + 1]}; + if ($doc->[$i] eq 'params') + { + ++$i; + @parametersList = @{$doc->[$i]}; + } + else + { + @parametersList = @{$doc->[$i]->{params}}; + } for my $parameter (@parametersList) { my $declname = $parameter->{parameters}->[0]->{name}; my $decltype = cleanupDoxygenType($paramInfo->{$declname}->{type}); - $paramDocs .= Text::Wrap::fill(' ', ' ', "$declname: " . formatDocComment($parameter->{doc}) . " (type: $decltype)") . "\n"; + $paramDocs .= Text::Wrap::fill(' ', ' ', "$declname: " . formatDocComment($parameter->{doc}) + . " (type: $decltype)") . "\n"; } - ++$i; } elsif ($doc->[$i]->{return}) { @@ -154,7 +163,7 @@ sub formatDocComment my $result = Text::Wrap::fill('', '', $bodyDocs); $result .= "\n\n" . $paramDocs if $paramDocs; $result .= "\n\n" . $returnDocs if $returnDocs; - + return $result; } diff --git a/libraries/wallet/wallet_results.cpp b/libraries/wallet/wallet_results.cpp index a47e28476f..02329f03b8 100644 --- a/libraries/wallet/wallet_results.cpp +++ b/libraries/wallet/wallet_results.cpp @@ -50,10 +50,8 @@ namespace graphene { namespace wallet { namespace detail { for( operation_detail& d : r ) { operation_history_object& i = d.op; - auto b = _remote_db->get_block_header(i.block_num); - FC_ASSERT(b); ss << i.block_num << " "; - ss << b->timestamp.to_iso_string() << " "; + ss << i.block_time.to_iso_string() << " "; ss << string(i.id) << " "; i.op.visit(operation_printer(ss, *this, i)); ss << " \n"; @@ -72,10 +70,8 @@ namespace graphene { namespace wallet { namespace detail { ss << "result_count : " << r.result_count << " \n"; for (operation_detail_ex& d : r.details) { operation_history_object& i = d.op; - auto b = _remote_db->get_block_header(i.block_num); - FC_ASSERT(b); ss << i.block_num << " "; - ss << b->timestamp.to_iso_string() << " "; + ss << i.block_time.to_iso_string() << " "; ss << string(i.id) << " "; i.op.visit(operation_printer(ss, *this, i)); ss << " transaction_id : "; diff --git a/libraries/wallet/wallet_voting.cpp b/libraries/wallet/wallet_voting.cpp index 3d30af356f..1b791f931a 100644 --- a/libraries/wallet/wallet_voting.cpp +++ b/libraries/wallet/wallet_voting.cpp @@ -24,7 +24,7 @@ #include "wallet_api_impl.hpp" #include -/**** +/* * Methods to handle voting / workers / committee */ diff --git a/programs/network_mapper/network_mapper.cpp b/programs/network_mapper/network_mapper.cpp index f623a46d06..2ec167b005 100644 --- a/programs/network_mapper/network_mapper.cpp +++ b/programs/network_mapper/network_mapper.cpp @@ -22,6 +22,7 @@ class peer_probe : public graphene::net::peer_connection_delegate bool _done = false; graphene::net::peer_connection_ptr _connection = graphene::net::peer_connection::make_shared(this); fc::promise::ptr _probe_complete_promise = fc::promise::create("probe_complete"); + fc::future _timeout_handler; fc::ip::endpoint _remote; graphene::net::node_id_t _node_id; @@ -50,6 +51,15 @@ class peer_probe : public graphene::net::peer_connection_delegate chain_id, fc::variant_object()); + constexpr uint16_t timeout = 180; // seconds + _timeout_handler = fc::schedule( [this]() { + wlog( "Communication with peer ${remote} took too long, closing connection", ("remote", _remote) ); + wdump( (_peers)(_peers.size()) ); + _timeout_handler = fc::future(); + _we_closed_connection = true; + _connection->close_connection(); + }, fc::time_point::now() + fc::seconds(timeout), "timeout_handler" ); + _connection->send_message(hello); } catch( const fc::exception& e ) { ilog( "Got exception when connecting to peer ${endpoint} ${e}", @@ -156,8 +166,9 @@ class peer_probe : public graphene::net::peer_connection_delegate { // Note: In rare cases, the peer may neither send us an address_message nor close the connection, // causing us to wait forever. - // We tolerate it, because this program (network_mapper) is not critical. + // In this case the timeout handler will close the connection. _done = true; + _timeout_handler.cancel(); _probe_complete_promise->set_value(); } @@ -230,7 +241,7 @@ int main(int argc, char** argv) const auto& update_info_by_address_info = [ &address_info_by_node_id, &my_node_id, &nodes_already_visited, &nodes_to_visit_set, &nodes_to_visit ] ( const graphene::net::address_info& info ) { - if (info.node_id == my_node_id) // We should not be in the list, just be defensive here + if( info.node_id == graphene::net::node_id_t(my_node_id) ) // We should not be in the list, be defensive return; if (nodes_already_visited.find(info.remote_endpoint) == nodes_already_visited.end() && nodes_to_visit_set.find(info.remote_endpoint) == nodes_to_visit_set.end()) @@ -361,7 +372,8 @@ int main(int argc, char** argv) constexpr uint16_t pair_depth = 2; for (auto& node_and_connections : connections_by_node_id) for (const graphene::net::address_info& this_connection : node_and_connections.second) - if( this_connection.node_id != my_node_id ) // We should not be in the list, just be defensive here + if( this_connection.node_id != graphene::net::node_id_t(my_node_id) ) // We should not be in the list, + // just be defensive here dot_stream << " \"" << fc::variant( node_and_connections.first, pair_depth ).as_string() << "\" -- \"" << fc::variant( this_connection.node_id, 1 ).as_string() << "\";\n"; diff --git a/sonar-project.properties b/sonar-project.properties index 9cd8574e9f..77e8a510b6 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,24 +2,30 @@ sonar.organization=bitshares-on-github sonar.projectKey=bitshares_bitshares-core sonar.projectName=BitShares-Core -sonar.projectDescription=BitShares Blockchain implementation and command-line interface -sonar.projectVersion=6.1.x +sonar.projectDescription=BitShares Blockchain node and command-line wallet +sonar.projectVersion=7.0.x sonar.host.url=https://sonarcloud.io -sonar.links.homepage=https://bitshares.org +sonar.links.homepage=https://bitshares.github.io sonar.links.ci=https://github.com/bitshares/bitshares-core/actions sonar.links.issue=https://github.com/bitshares/bitshares-core/issues sonar.links.scm=https://github.com/bitshares/bitshares-core/tree/master +# Note: +# According to docs, sonar.tests is ignored by the C/C++/Objective-C analyzer. +# See https://docs.sonarcloud.io/advanced-setup/languages/c-c-objective-c/#language-specific-properties sonar.tests=tests + sonar.exclusions=programs/build_helper/**/*,libraries/fc/**/*,libraries/egenesis/egenesis_full.cpp sonar.sources=libraries,programs sonar.cfamily.build-wrapper-output=bw-output -sonar.cfamily.gcov.reportsPath=. -sonar.cfamily.threads=2 -sonar.cfamily.cache.enabled=true -sonar.cfamily.cache.path=sonar_cache + +# Note: +# It is hard to get the gcov sensor working, but gcovr works. +# See https://community.sonarsource.com/t/code-coverage-incorrect-for-c-gcov-project/41837/5 +#sonar.cfamily.gcov.reportsPath=. +sonar.coverageReportPaths=coverage.xml # Decide which tree the current build belongs to in SonarCloud. # Managed by the `set_sonar_branch*` script(s) when building with CI. diff --git a/tests/cli/main.cpp b/tests/cli/main.cpp index 41b3b632c8..585b2dd416 100644 --- a/tests/cli/main.cpp +++ b/tests/cli/main.cpp @@ -1246,8 +1246,8 @@ BOOST_FIXTURE_TEST_CASE( cli_confidential_tx_test, cli_fixture ) // then confirm that balances are received, and then analyze the range // prooofs to make sure the mantissa length does not reveal approximate // balance (issue #480). - std::map to_list = {{"alice",100000000000}, - {"bob", 1000000000}}; + std::map to_list = {{"alice",100000000000LL}, + {"bob", 1000000000LL}}; vector bconfs; auto core_asset = W.get_asset("1.3.0"); BOOST_TEST_MESSAGE("Sending blind transactions to alice and bob"); diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index a863964479..63bf0b7fc4 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -407,7 +407,6 @@ std::shared_ptr database_fixture_base::in } // load ES or AH, but not both if(fixture.current_test_name == "elasticsearch_account_history" || - fixture.current_test_name == "elasticsearch_suite" || fixture.current_test_name == "elasticsearch_history_api") { fixture.app.register_plugin(true); @@ -429,7 +428,7 @@ std::shared_ptr database_fixture_base::in fixture.app.register_plugin(true); } - if(fixture.current_test_name == "elasticsearch_objects" || fixture.current_test_name == "elasticsearch_suite") { + if( fixture.current_test_name == "elasticsearch_objects" ) { fixture.app.register_plugin(true); fc::set_option( options, "es-objects-elasticsearch-url", GRAPHENE_TESTING_ES_URL ); @@ -576,14 +575,17 @@ void database_fixture_base::verify_asset_supplies( const database& db ) if( for_sale.asset_id == asset_id_type() && !o.is_settled_debt ) // Note: CORE asset in settled debt is not counted in account_stats.total_core_in_orders core_in_orders += for_sale.amount; - total_balances[for_sale.asset_id] += for_sale.amount; total_balances[asset_id_type()] += o.deferred_fee; total_balances[o.deferred_paid_fee.asset_id] += o.deferred_paid_fee.amount; if( o.is_settled_debt ) { - total_debts[o.receive_asset_id()] += o.sell_price.quote.amount; - BOOST_CHECK_EQUAL( o.sell_price.base.amount.value, for_sale.amount.value ); + const auto& bitasset = o.receive_asset_id()(db).bitasset_data(db); + BOOST_CHECK_LE( o.for_sale.value, bitasset.individual_settlement_fund.value ); + auto settled_debt = asset( bitasset.individual_settlement_debt, o.receive_asset_id() ); + BOOST_CHECK_EQUAL( settled_debt.multiply_and_round_up( o.sell_price ).amount.value, o.for_sale.value ); } + else + total_balances[for_sale.asset_id] += for_sale.amount; } for( const call_order_object& o : db.get_index_type().indices() ) { @@ -603,7 +605,7 @@ void database_fixture_base::verify_asset_supplies( const database& db ) total_balances[bad.options.short_backing_asset] += bad.settlement_fund; total_balances[bad.options.short_backing_asset] += bad.individual_settlement_fund; total_balances[bad.options.short_backing_asset] += dasset_obj.accumulated_collateral_fees; - if( !bad.has_settlement() ) // Note: if asset has been globally settled, do not check total debt + if( !bad.is_globally_settled() ) // Note: if asset has been globally settled, do not check total debt total_debts[bad.asset_id] += bad.individual_settlement_debt; } total_balances[asset_obj.get_id()] += dasset_obj.confidential_supply.value; @@ -1161,28 +1163,32 @@ digest_type database_fixture_base::digest( const transaction& tx ) return tx.digest(); } -const limit_order_object* database_fixture_base::create_sell_order(account_id_type user, const asset& amount, const asset& recv, - const time_point_sec order_expiration, - const price& fee_core_exchange_rate ) +limit_order_create_operation database_fixture_base::make_limit_order_create_op( + const account_id_type& user, const asset& amount, const asset& recv, + const time_point_sec& order_expiration, + const optional< vector< limit_order_auto_action > >& on_fill ) const { - auto r = create_sell_order( user(db), amount, recv, order_expiration, fee_core_exchange_rate ); - verify_asset_supplies(db); - return r; + limit_order_create_operation buy_order; + buy_order.seller = user; + buy_order.amount_to_sell = amount; + buy_order.min_to_receive = recv; + buy_order.expiration = order_expiration; + buy_order.extensions.value.on_fill = on_fill; + return buy_order; } -const limit_order_object* database_fixture_base::create_sell_order( const account_object& user, const asset& amount, const asset& recv, - const time_point_sec order_expiration, - const price& fee_core_exchange_rate ) +const limit_order_object* database_fixture_base::create_sell_order( + const account_id_type& user, const asset& amount, const asset& recv, + const time_point_sec& order_expiration, + const price& fee_core_exchange_rate, + const optional< vector< limit_order_auto_action > >& on_fill ) { set_expiration( db, trx ); trx.operations.clear(); - limit_order_create_operation buy_order; - buy_order.seller = user.id; - buy_order.amount_to_sell = amount; - buy_order.min_to_receive = recv; - buy_order.expiration = order_expiration; - trx.operations.push_back(buy_order); + limit_order_create_operation buy_order = make_limit_order_create_op( user, amount, recv, order_expiration, + on_fill ); + trx.operations = {buy_order}; for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op, fee_core_exchange_rate); trx.validate(); auto processed = PUSH_TX(db, trx, ~0); @@ -1191,6 +1197,60 @@ const limit_order_object* database_fixture_base::create_sell_order( const accoun return db.find( processed.operation_results[0].get() ); } +const limit_order_object* database_fixture_base::create_sell_order( + const account_object& user, const asset& amount, const asset& recv, + const time_point_sec& order_expiration, + const price& fee_core_exchange_rate, + const optional< vector< limit_order_auto_action > >& on_fill ) +{ + return create_sell_order( user.get_id(), amount, recv, order_expiration, fee_core_exchange_rate ); +} + +limit_order_update_operation database_fixture_base::make_limit_order_update_op( + const account_id_type& seller_id, + const limit_order_id_type& order_id, + const fc::optional& new_price, + const fc::optional& delta_amount, + const fc::optional& new_expiration, + const optional< vector< limit_order_auto_action > >& on_fill )const +{ + limit_order_update_operation update_order; + update_order.seller = seller_id; + update_order.order = order_id; + update_order.new_price = new_price; + update_order.delta_amount_to_sell = delta_amount; + update_order.new_expiration = new_expiration; + update_order.on_fill = on_fill; + return update_order; +} + +void database_fixture_base::update_limit_order(const limit_order_object& order, + const fc::optional& new_price, + const fc::optional& delta_amount, + const fc::optional& new_expiration, + const price& fee_core_exchange_rate, + const optional< vector< limit_order_auto_action > >& on_fill ) +{ + limit_order_update_operation update_order = make_limit_order_update_op( order.seller, order.get_id(), new_price, + delta_amount, new_expiration, on_fill ); + trx.operations = {update_order}; + for(auto& op : trx.operations) db.current_fee_schedule().set_fee(op, fee_core_exchange_rate); + trx.validate(); + auto processed = PUSH_TX(db, trx, ~0); + trx.operations.clear(); + verify_asset_supplies(db); +} + +void database_fixture_base::update_limit_order(const limit_order_id_type& order_id, + const fc::optional& new_price, + const fc::optional& delta_amount, + const fc::optional& new_expiration, + const price& fee_core_exchange_rate, + const optional< vector< limit_order_auto_action > >& on_fill ) +{ + update_limit_order( order_id(db), new_price, delta_amount, new_expiration, fee_core_exchange_rate, on_fill ); +} + asset database_fixture_base::cancel_limit_order( const limit_order_object& order ) { limit_order_cancel_operation cancel_order; @@ -1202,7 +1262,7 @@ asset database_fixture_base::cancel_limit_order( const limit_order_object& order trx.validate(); auto processed = PUSH_TX(db, trx, ~0); trx.operations.clear(); - verify_asset_supplies(db); + verify_asset_supplies(db); return processed.operation_results[0].get(); } @@ -1534,6 +1594,35 @@ generic_operation_result database_fixture_base::delete_liquidity_pool( account_i return op_result.get(); } +liquidity_pool_update_operation database_fixture_base::make_liquidity_pool_update_op( account_id_type account, + liquidity_pool_id_type pool, + optional taker_fee_percent, + optional withdrawal_fee_percent )const +{ + liquidity_pool_update_operation op; + op.account = account; + op.pool = pool; + op.taker_fee_percent = taker_fee_percent; + op.withdrawal_fee_percent = withdrawal_fee_percent; + return op; +} + +void database_fixture_base::update_liquidity_pool( account_id_type account, + liquidity_pool_id_type pool, + optional taker_fee_percent, + optional withdrawal_fee_percent ) +{ + liquidity_pool_update_operation op = make_liquidity_pool_update_op( account, pool, taker_fee_percent, + withdrawal_fee_percent ); + trx.operations.clear(); + trx.operations.push_back( op ); + + for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o); + trx.validate(); + set_expiration( db, trx ); + PUSH_TX(db, trx, ~0); +} + liquidity_pool_deposit_operation database_fixture_base::make_liquidity_pool_deposit_op( account_id_type account, liquidity_pool_id_type pool, const asset& amount_a, const asset& amount_b )const @@ -1889,7 +1978,8 @@ void database_fixture_base::update_credit_offer( account_id_type account, credit credit_offer_accept_operation database_fixture_base::make_credit_offer_accept_op( account_id_type account, credit_offer_id_type offer_id, const asset& borrow_amount, const asset& collateral, - uint32_t max_fee_rate, uint32_t min_duration )const + uint32_t max_fee_rate, uint32_t min_duration, + const optional& auto_repay )const { credit_offer_accept_operation op; op.borrower = account; @@ -1898,16 +1988,18 @@ credit_offer_accept_operation database_fixture_base::make_credit_offer_accept_op op.collateral = collateral; op.max_fee_rate = max_fee_rate; op.min_duration_seconds = min_duration; + op.extensions.value.auto_repay = auto_repay; return op; } const credit_deal_object& database_fixture_base::borrow_from_credit_offer( account_id_type account, credit_offer_id_type offer_id, const asset& borrow_amount, const asset& collateral, - uint32_t max_fee_rate, uint32_t min_duration ) + uint32_t max_fee_rate, uint32_t min_duration, + const optional& auto_repay ) { credit_offer_accept_operation op = make_credit_offer_accept_op( account, offer_id, borrow_amount, collateral, - max_fee_rate, min_duration ); + max_fee_rate, min_duration, auto_repay ); trx.operations.clear(); trx.operations.push_back( op ); @@ -1956,6 +2048,33 @@ extendable_operation_result_dtl database_fixture_base::repay_credit_deal( return op_result.get().value; } +credit_deal_update_operation database_fixture_base::make_credit_deal_update_op( + account_id_type account, credit_deal_id_type deal_id, + uint8_t auto_repay )const +{ + credit_deal_update_operation op; + op.account = account; + op.deal_id = deal_id; + op.auto_repay = auto_repay; + return op; +} + +void database_fixture_base::update_credit_deal( + account_id_type account, credit_deal_id_type deal_id, + uint8_t auto_repay ) +{ + credit_deal_update_operation op = make_credit_deal_update_op( account, deal_id, auto_repay ); + trx.operations.clear(); + trx.operations.push_back( op ); + + for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o); + trx.validate(); + set_expiration( db, trx ); + PUSH_TX(db, trx, ~0); + trx.operations.clear(); + verify_asset_supplies(db); +} + void database_fixture_base::enable_fees() { @@ -2163,24 +2282,24 @@ flat_map< uint64_t, graphene::chain::fee_parameters > database_fixture_base::get { flat_map ret_val; - htlc_create_operation::fee_parameters_type create_param; + htlc_create_operation::fee_params_t create_param; create_param.fee_per_day = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; create_param.fee = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; ret_val[((operation)htlc_create_operation()).which()] = create_param; - htlc_redeem_operation::fee_parameters_type redeem_param; + htlc_redeem_operation::fee_params_t redeem_param; redeem_param.fee = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; redeem_param.fee_per_kb = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; ret_val[((operation)htlc_redeem_operation()).which()] = redeem_param; - htlc_extend_operation::fee_parameters_type extend_param; + htlc_extend_operation::fee_params_t extend_param; extend_param.fee = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; extend_param.fee_per_day = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; ret_val[((operation)htlc_extend_operation()).which()] = extend_param; // set the transfer kb fee to something other than default, to verify we're looking // at the correct fee - transfer_operation::fee_parameters_type transfer_param; + transfer_operation::fee_params_t transfer_param; transfer_param.price_per_kbyte *= 2; ret_val[ ((operation)transfer_operation()).which() ] = transfer_param; diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 340031b38e..3629aa7aa4 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -222,6 +222,7 @@ struct database_fixture_base { bool hf2467 = false; // Note: used by hf core-2281 too, assuming hf core-2281 and core-2467 occur at the same time bool hf2481 = false; bool bsip77 = false; + bool hf2595 = false; string es_index_prefix; ///< Index prefix for elasticsearch plugin string es_obj_index_prefix; ///< Index prefix for es_objects plugin @@ -410,12 +411,37 @@ struct database_fixture_base { uint64_t fund( const account_object& account, const asset& amount = asset(500000) ); digest_type digest( const transaction& tx ); void sign( signed_transaction& trx, const fc::ecc::private_key& key ); - const limit_order_object* create_sell_order( account_id_type user, const asset& amount, const asset& recv, - const time_point_sec order_expiration = time_point_sec::maximum(), - const price& fee_core_exchange_rate = price::unit_price() ); + limit_order_create_operation make_limit_order_create_op( + const account_id_type& user, const asset& amount, const asset& recv, + const time_point_sec& order_expiration = time_point_sec::maximum(), + const optional< vector< limit_order_auto_action > >& on_fill = {} ) const; + const limit_order_object* create_sell_order( const account_id_type& user, const asset& amount, const asset& recv, + const time_point_sec& order_expiration = time_point_sec::maximum(), + const price& fee_core_exchange_rate = price::unit_price(), + const optional< vector< limit_order_auto_action > >& on_fill = {} ); const limit_order_object* create_sell_order( const account_object& user, const asset& amount, const asset& recv, - const time_point_sec order_expiration = time_point_sec::maximum(), - const price& fee_core_exchange_rate = price::unit_price() ); + const time_point_sec& order_expiration = time_point_sec::maximum(), + const price& fee_core_exchange_rate = price::unit_price(), + const optional< vector< limit_order_auto_action > >& on_fill = {} ); + limit_order_update_operation make_limit_order_update_op( + const account_id_type& seller_id, + const limit_order_id_type& order_id, + const fc::optional& new_price = {}, + const fc::optional& delta_amount = {}, + const fc::optional& new_expiration = {}, + const optional< vector< limit_order_auto_action > >& on_fill = {} )const; + void update_limit_order(const limit_order_object& order, + const fc::optional& new_price = {}, + const fc::optional& delta_amount = {}, + const fc::optional& new_expiration = {}, + const price& fee_core_exchange_rate = price::unit_price(), + const optional< vector< limit_order_auto_action > >& on_fill = {} ); + void update_limit_order(const limit_order_id_type& order_id, + const fc::optional& new_price = {}, + const fc::optional& delta_amount = {}, + const fc::optional& new_expiration = {}, + const price& fee_core_exchange_rate = price::unit_price(), + const optional< vector< limit_order_auto_action > >& on_fill = {} ); asset cancel_limit_order( const limit_order_object& order ); void transfer( account_id_type from, account_id_type to, const asset& amount, const asset& fee = asset() ); void transfer( const account_object& from, const account_object& to, const asset& amount, const asset& fee = asset() ); @@ -440,6 +466,13 @@ struct database_fixture_base { liquidity_pool_delete_operation make_liquidity_pool_delete_op( account_id_type account, liquidity_pool_id_type pool )const; generic_operation_result delete_liquidity_pool( account_id_type account, liquidity_pool_id_type pool ); + liquidity_pool_update_operation make_liquidity_pool_update_op( account_id_type account, + liquidity_pool_id_type pool, + optional taker_fee_percent, + optional withdrawal_fee_percent )const; + void update_liquidity_pool( account_id_type account, liquidity_pool_id_type pool, + optional taker_fee_percent, + optional withdrawal_fee_percent ); liquidity_pool_deposit_operation make_liquidity_pool_deposit_op( account_id_type account, liquidity_pool_id_type pool, const asset& amount_a, const asset& amount_b )const; @@ -519,17 +552,22 @@ struct database_fixture_base { account_id_type account, credit_offer_id_type offer_id, const asset& borrow_amount, const asset& collateral, uint32_t max_fee_rate = GRAPHENE_FEE_RATE_DENOM, - uint32_t min_duration = 0 )const; + uint32_t min_duration = 0, const optional& auto_repay = {} )const; const credit_deal_object& borrow_from_credit_offer( account_id_type account, credit_offer_id_type offer_id, const asset& borrow_amount, const asset& collateral, uint32_t max_fee_rate = GRAPHENE_FEE_RATE_DENOM, - uint32_t min_duration = 0 ); + uint32_t min_duration = 0, const optional& auto_repay = {} ); credit_deal_repay_operation make_credit_deal_repay_op( account_id_type account, credit_deal_id_type deal_id, const asset& repay_amount, const asset& credit_fee )const; extendable_operation_result_dtl repay_credit_deal( account_id_type account, credit_deal_id_type deal_id, const asset& repay_amount, const asset& credit_fee ); + credit_deal_update_operation make_credit_deal_update_op( + account_id_type account, credit_deal_id_type deal_id, + uint8_t auto_repay )const; + void update_credit_deal( account_id_type account, credit_deal_id_type deal_id, + uint8_t auto_repay ); /** * NOTE: This modifies the database directly. You will probably have to call this each time you diff --git a/tests/common/elasticsearch.cpp b/tests/common/elasticsearch.cpp index 1393f35872..44bcd0ff41 100644 --- a/tests/common/elasticsearch.cpp +++ b/tests/common/elasticsearch.cpp @@ -48,7 +48,6 @@ bool checkES(ES& es) if(doCurl(curl_request).empty()) return false; return true; - } std::string getESVersion(ES& es) @@ -69,6 +68,7 @@ void checkESVersion7OrAbove(ES& es, bool& result) noexcept static const int64_t version_7 = 7; try { const auto es_version = graphene::utilities::getESVersion(es); + ilog( "ES version detected: ${v}", ("v", es_version) ); auto dot_pos = es_version.find('.'); result = ( std::stoi(es_version.substr(0,dot_pos)) >= version_7 ); } @@ -100,10 +100,29 @@ bool deleteAll(ES& es) curl_request.type = "DELETE"; auto curl_response = doCurl(curl_request); - if(curl_response.empty()) + if( curl_response.empty() ) + { + wlog( "Empty ES response" ); + return false; + } + + // Check errors in response + try + { + fc::variant j = fc::json::from_string(curl_response); + if( j.is_object() && j.get_object().contains("error") ) + { + wlog( "ES returned an error: ${r}", ("r", curl_response) ); + return false; + } + } + catch( const fc::exception& e ) + { + wlog( "Error while checking ES response ${r}", ("r", curl_response) ); + wdump( (e.to_detail_string()) ); return false; - else - return true; + } + return true; } std::string getEndPoint(ES& es) @@ -147,6 +166,12 @@ std::string doCurl(CurlRequest& curl) curl_easy_setopt(curl.handler, CURLOPT_USERPWD, curl.auth.c_str()); curl_easy_perform(curl.handler); + long code; + curl_easy_getinfo( curl.handler, CURLINFO_RESPONSE_CODE, &code ); + + if( 200 != code ) + wlog( "doCurl response [${code}] ${msg}", ("code", ((int64_t)code))("msg", CurlReadBuffer) ); + return CurlReadBuffer; } diff --git a/tests/elasticsearch/main.cpp b/tests/elasticsearch/main.cpp index 6fb4fbb266..e3c6ca7daa 100644 --- a/tests/elasticsearch/main.cpp +++ b/tests/elasticsearch/main.cpp @@ -71,7 +71,7 @@ BOOST_AUTO_TEST_CASE(elasticsearch_account_history) { generate_block(); string query = "{ \"query\" : { \"bool\" : { \"must\" : [{\"match_all\": {}}] } } }"; - es.endpoint = es.index_prefix + "*/_doc/_count"; + es.endpoint = es.index_prefix + "*/_count"; es.query = query; string res; @@ -85,7 +85,7 @@ BOOST_AUTO_TEST_CASE(elasticsearch_account_history) { return (total == "5"); }); - es.endpoint = es.index_prefix + "*/_doc/_search"; + es.endpoint = es.index_prefix + "*/_search"; res = graphene::utilities::simpleQuery(es); j = fc::json::from_string(res); auto first_id = j["hits"]["hits"][size_t(0)]["_id"].as_string(); @@ -95,7 +95,7 @@ BOOST_AUTO_TEST_CASE(elasticsearch_account_history) { auto willie = create_account("willie"); generate_block(); - es.endpoint = es.index_prefix + "*/_doc/_count"; + es.endpoint = es.index_prefix + "*/_count"; fc::wait_for( ES_WAIT_TIME, [&]() { res = graphene::utilities::simpleQuery(es); @@ -129,6 +129,8 @@ BOOST_AUTO_TEST_CASE(elasticsearch_account_history) { BOOST_CHECK_EQUAL(last_transfer_amount, "300"); auto last_transfer_payer = j["_source"]["operation_history"]["fee_payer"].as_string(); BOOST_CHECK_EQUAL(last_transfer_payer, "1.2.0"); + auto is_virtual = j["_source"]["operation_history"]["is_virtual"].as_bool(); + BOOST_CHECK( !is_virtual ); // To test credit offers generate_blocks( HARDFORK_CORE_2362_TIME ); @@ -197,7 +199,7 @@ BOOST_AUTO_TEST_CASE(elasticsearch_account_history) { generate_block(); - es.endpoint = es.index_prefix + "*/_doc/_count"; + es.endpoint = es.index_prefix + "*/_count"; fc::wait_for( ES_WAIT_TIME, [&]() { res = graphene::utilities::simpleQuery(es); j = fc::json::from_string(res); @@ -228,6 +230,10 @@ BOOST_AUTO_TEST_CASE(elasticsearch_objects) { // The head block number is 1 BOOST_CHECK_EQUAL( db.head_block_num(), 1u ); + generate_blocks( HARDFORK_CORE_2535_TIME ); // For Order-Sends-Take-Profit-Order + generate_block(); + set_expiration( db, trx ); + // delete all first, this will delete genesis data and data inserted at block 1 auto delete_objects = graphene::utilities::deleteAll(es); BOOST_REQUIRE(delete_objects); // require successful deletion @@ -241,7 +247,7 @@ BOOST_AUTO_TEST_CASE(elasticsearch_objects) { generate_block(); string query = "{ \"query\" : { \"bool\" : { \"must\" : [{\"match_all\": {}}] } } }"; - es.endpoint = es.index_prefix + "*/_doc/_count"; + es.endpoint = es.index_prefix + "*/_count"; es.query = query; string res; @@ -255,14 +261,14 @@ BOOST_AUTO_TEST_CASE(elasticsearch_objects) { return (total == "2"); }); - es.endpoint = es.index_prefix + "asset/_doc/_search"; + es.endpoint = es.index_prefix + "asset/_search"; res = graphene::utilities::simpleQuery(es); j = fc::json::from_string(res); auto first_id = j["hits"]["hits"][size_t(0)]["_source"]["symbol"].as_string(); BOOST_CHECK_EQUAL(first_id, "USD"); auto bitasset_data_id = j["hits"]["hits"][size_t(0)]["_source"]["bitasset_data_id"].as_string(); - es.endpoint = es.index_prefix + "bitasset/_doc/_search"; + es.endpoint = es.index_prefix + "bitasset/_search"; es.query = "{ \"query\" : { \"bool\": { \"must\" : [{ \"term\": { \"object_id\": \"" + bitasset_data_id + "\"}}] } } }"; res = graphene::utilities::simpleQuery(es); @@ -270,12 +276,16 @@ BOOST_AUTO_TEST_CASE(elasticsearch_objects) { auto bitasset_object_id = j["hits"]["hits"][size_t(0)]["_source"]["object_id"].as_string(); BOOST_CHECK_EQUAL(bitasset_object_id, bitasset_data_id); + // fee_asset, spread, size, expiration, repeat + create_take_profit_order_action tpa1 { asset_id_type(), 300, 9900, 86400, true }; + vector on_fill_1 { tpa1 }; // create a limit order that expires at the next maintenance time create_sell_order( account_id_type(), asset(1), asset(1, usd_id), - db.get_dynamic_global_properties().next_maintenance_time ); + db.get_dynamic_global_properties().next_maintenance_time, + price::unit_price(), on_fill_1 ); generate_block(); - es.endpoint = es.index_prefix + "limitorder/_doc/_count"; + es.endpoint = es.index_prefix + "limitorder/_count"; es.query = ""; fc::wait_for( ES_WAIT_TIME, [&]() { res = graphene::utilities::getEndPoint(es); @@ -293,7 +303,7 @@ BOOST_AUTO_TEST_CASE(elasticsearch_objects) { generate_blocks( db.get_dynamic_global_properties().next_maintenance_time ); generate_block(); - es.endpoint = es.index_prefix + "budget/_doc/_count"; + es.endpoint = es.index_prefix + "budget/_count"; es.query = ""; fc::wait_for( ES_WAIT_TIME, [&]() { res = graphene::utilities::getEndPoint(es); @@ -307,7 +317,7 @@ BOOST_AUTO_TEST_CASE(elasticsearch_objects) { return (total == "1"); // new record inserted at the first maintenance block }); - es.endpoint = es.index_prefix + "limitorder/_doc/_count"; + es.endpoint = es.index_prefix + "limitorder/_count"; es.query = ""; fc::wait_for( ES_WAIT_TIME, [&]() { res = graphene::utilities::getEndPoint(es); @@ -329,39 +339,6 @@ BOOST_AUTO_TEST_CASE(elasticsearch_objects) { } } -BOOST_AUTO_TEST_CASE(elasticsearch_suite) { - try { - - CURL *curl; // curl handler - curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); - - graphene::utilities::ES es; - es.curl = curl; - es.elasticsearch_url = GRAPHENE_TESTING_ES_URL; - es.index_prefix = es_index_prefix; - auto delete_account_history = graphene::utilities::deleteAll(es); - BOOST_REQUIRE(delete_account_history); // require successful deletion - - graphene::utilities::ES es_obj; - es_obj.curl = curl; - es_obj.elasticsearch_url = GRAPHENE_TESTING_ES_URL; - es_obj.index_prefix = es_obj_index_prefix; - auto delete_objects = graphene::utilities::deleteAll(es_obj); - BOOST_REQUIRE(delete_objects); // require successful deletion - - if(delete_account_history && delete_objects) { // all records deleted - - - } - // Note: this test case ends too quickly, sometimes causing an memory access violation on cleanup - } - catch (fc::exception &e) { - edump((e.to_detail_string())); - throw; - } -} - BOOST_AUTO_TEST_CASE(elasticsearch_history_api) { try { CURL *curl; // curl handler @@ -373,6 +350,10 @@ BOOST_AUTO_TEST_CASE(elasticsearch_history_api) { es.elasticsearch_url = GRAPHENE_TESTING_ES_URL; es.index_prefix = es_index_prefix; + generate_blocks( HARDFORK_CORE_2535_TIME ); // For Order-Sends-Take-Profit-Order + generate_block(); + set_expiration( db, trx ); + auto delete_account_history = graphene::utilities::deleteAll(es); BOOST_REQUIRE(delete_account_history); // require successful deletion @@ -390,8 +371,8 @@ BOOST_AUTO_TEST_CASE(elasticsearch_history_api) { generate_block(); + // Test history APIs graphene::app::history_api hist_api(app); - app.enable_plugin("elasticsearch"); // f(A, 0, 4, 9) = { 5, 3, 1, 0 } auto histories = hist_api.get_account_history( @@ -408,6 +389,9 @@ BOOST_AUTO_TEST_CASE(elasticsearch_history_api) { BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u); BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u); + BOOST_CHECK( !histories[0].is_virtual ); + BOOST_CHECK( histories[0].block_time == db.head_block_time() ); + // f(A, 0, 4, 6) = { 5, 3, 1, 0 } histories = hist_api.get_account_history("1.2.0", operation_history_id_type(), 4, operation_history_id_type(6)); BOOST_REQUIRE_EQUAL(histories.size(), 4u); @@ -660,7 +644,8 @@ BOOST_AUTO_TEST_CASE(elasticsearch_history_api) { BOOST_CHECK_EQUAL(histories.size(), 0u); // create a new account C = alice { 7 } - create_account("alice"); + auto alice = create_account("alice"); + account_id_type alice_id = alice.get_id(); generate_block(); @@ -685,6 +670,61 @@ BOOST_AUTO_TEST_CASE(elasticsearch_history_api) { BOOST_CHECK_EQUAL(histories[2].id.instance(), 3u); BOOST_CHECK_EQUAL(histories[3].id.instance(), 1u); BOOST_CHECK_EQUAL(histories[4].id.instance(), 0u); + + // Ugly test to cover elasticsearch_plugin::get_operation_by_id() + if( !app.elasticsearch_thread ) + app.elasticsearch_thread = std::make_shared("elasticsearch"); + auto es_plugin = app.get_plugin< graphene::elasticsearch::elasticsearch_plugin >("elasticsearch"); + auto his_obj7 = app.elasticsearch_thread->async([&es_plugin]() { + return es_plugin->get_operation_by_id( operation_history_id_type(7) ); + }, "thread invoke for method " BOOST_PP_STRINGIZE(method_name)).wait(); + BOOST_REQUIRE( his_obj7.op.is_type() ); + BOOST_CHECK_EQUAL( his_obj7.op.get().name, "alice" ); + + // Test virtual operation + + // Prepare funds + transfer( account_id_type()(db), alice_id(db), asset(100) ); + // fee_asset, spread, size, expiration, repeat + create_take_profit_order_action tpa1 { asset_id_type(), 100, 10000, 86400, false }; + vector on_fill_1 { tpa1 }; + // Create a limit order that expires in 300 seconds + create_sell_order( alice_id, asset(1), asset(1, asset_id_type(1)), db.head_block_time() + 300, + price::unit_price(), on_fill_1 ); + + generate_block(); + + // f(C, 0, 4, 0) = { 9, 8, 7 } + fc::wait_for( ES_WAIT_TIME, [&]() { + histories = hist_api.get_account_history( + "alice", operation_history_id_type(0), 4, operation_history_id_type(0)); + return (histories.size() == 3u); + }); + BOOST_REQUIRE_EQUAL(histories.size(), 3u); + BOOST_CHECK( histories[0].op.is_type() ); + BOOST_CHECK( !histories[0].is_virtual ); + BOOST_CHECK( histories[0].block_time == db.head_block_time() ); + BOOST_CHECK( histories[1].op.is_type() ); + BOOST_CHECK( !histories[1].is_virtual ); + + // Let the limit order expire + generate_blocks( db.head_block_time() + 300 ); + generate_block(); + + // f(C, 0, 4, 0) = { 10, 9, 8, 7 } + fc::wait_for( ES_WAIT_TIME, [&]() { + histories = hist_api.get_account_history( + "alice", operation_history_id_type(0), 4, operation_history_id_type(0)); + return (histories.size() == 4u); + }); + BOOST_REQUIRE_EQUAL(histories.size(), 4u); + BOOST_CHECK( histories[0].op.is_type() ); + BOOST_CHECK( histories[0].is_virtual ); + BOOST_CHECK( histories[1].op.is_type() ); + BOOST_CHECK( !histories[1].is_virtual ); + BOOST_CHECK( histories[2].op.is_type() ); + BOOST_CHECK( !histories[2].is_virtual ); + } } catch (fc::exception &e) { diff --git a/tests/tests/api_limit_tests.cpp b/tests/tests/api_limit_tests.cpp index 18167074b4..c0cfac686a 100644 --- a/tests/tests/api_limit_tests.cpp +++ b/tests/tests/api_limit_tests.cpp @@ -445,7 +445,7 @@ BOOST_AUTO_TEST_CASE(api_limit_get_collateral_bids) { // this sell order is designed to trigger a black swan create_sell_order( borrower2_id(db), swan(db).amount(1), back(db).amount(3) ); - BOOST_CHECK( swan(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan(db).bitasset_data(db).is_globally_settled() ); //making 3 collateral bids for (int i=0; i<3; i++) { diff --git a/tests/tests/basic_tests.cpp b/tests/tests/basic_tests.cpp index 1769eb6e39..459bfa180f 100644 --- a/tests/tests/basic_tests.cpp +++ b/tests/tests/basic_tests.cpp @@ -135,6 +135,59 @@ BOOST_AUTO_TEST_CASE( valid_symbol_test ) BOOST_CHECK( is_valid_symbol( "AAA000AAA" ) ); } +BOOST_AUTO_TEST_CASE( object_id_test ) +{ + + uint64_t u56 = ((uint64_t)1)<<56; + BOOST_CHECK_THROW( object_id_type( 1, 0, u56 ), fc::exception ); + + object_id_type o102( 1, 0, 2 ); + BOOST_CHECK_THROW( (object_id<1,1>( o102 )), fc::exception ); + BOOST_CHECK_THROW( (object_id<2,1>( o102 )), fc::exception ); + BOOST_CHECK_THROW( (object_id<2,0>( o102 )), fc::exception ); + + BOOST_CHECK_THROW( (object_id<1,0>( u56 )), fc::exception ); + + object_id_type o1; + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("1") ), o1 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string(".1") ), o1 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("1.1") ), o1 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("1..1") ), o1 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("1.1.") ), o1 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("256.1.1") ), o1 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("1.256.1") ), o1 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("256.256.1") ), o1 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("1.1.") + std::to_string(u56) ), o1 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("1.1.a") ), o1 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("1.a.1") ), o1 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("a.1.1") ), o1 ), fc::exception ); + + from_variant( fc::variant( std::string("1.1.1234567") ), o1 ); + BOOST_CHECK( o1 == object_id_type( 1, 1, 1234567 ) ); + + object_id<1,1> o2; + BOOST_CHECK( o2 != o1 ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("1") ), o2 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string(".1") ), o2 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("1.1") ), o2 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("1..1") ), o2 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("1.1.") ), o2 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("2.1.1") ), o2 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("1.2.1") ), o2 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("2.2.1") ), o2 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("256.1.1") ), o2 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("1.256.1") ), o2 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("256.256.1") ), o2 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("1.1.") + std::to_string(u56) ), o2 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("1.1.a") ), o2 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("1.a.1") ), o2 ), fc::exception ); + BOOST_CHECK_THROW( from_variant( fc::variant( std::string("a.1.1") ), o2 ), fc::exception ); + + from_variant( fc::variant( std::string("1.1.1234567") ), o2 ); + BOOST_CHECK( o2 == o1 ); + +} + BOOST_AUTO_TEST_CASE( price_test ) { auto price_max = []( uint32_t a, uint32_t b ) @@ -471,7 +524,7 @@ BOOST_AUTO_TEST_CASE( merkle_root ) auto c = []( const digest_type& digest ) -> checksum_type { return checksum_type::hash( digest ); }; - + auto d = []( const digest_type& left, const digest_type& right ) -> digest_type { return digest_type::hash( std::make_pair( left, right ) ); }; @@ -486,7 +539,7 @@ BOOST_AUTO_TEST_CASE( merkle_root ) /* A=d(0,1) - / \ + / \ 0 1 */ @@ -600,7 +653,7 @@ BOOST_AUTO_TEST_CASE( merkle_root ) /* _____________O=d(M,N)______________ - / \ + / \ __M=d(I,J)__ N=K / \ / I=d(A,B) J=d(C,D) K=E @@ -621,7 +674,7 @@ BOOST_AUTO_TEST_CASE( merkle_root ) /* _____________O=d(M,N)______________ - / \ + / \ __M=d(I,J)__ N=K / \ / I=d(A,B) J=d(C,D) K=E diff --git a/tests/tests/bsip85_tests.cpp b/tests/tests/bsip85_tests.cpp index fc76ec70fd..57bad258cb 100644 --- a/tests/tests/bsip85_tests.cpp +++ b/tests/tests/bsip85_tests.cpp @@ -156,7 +156,7 @@ BOOST_AUTO_TEST_CASE( bsip85_maker_fee_discount_test ) int64_t core_maker_refund = usd_maker_refund == 0 ? 0 : core_create_fee * 1123 / 10000; fee_parameters::flat_set_type new_fees; - limit_order_create_operation::fee_parameters_type create_fee_params; + limit_order_create_operation::fee_params_t create_fee_params; create_fee_params.fee = order_create_fee; new_fees.insert( create_fee_params ); diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index d6047adcc3..c2b3781c99 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -801,8 +801,8 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) using bsrm_type = bitasset_options::black_swan_response_type; BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // add a price feed publisher and publish a feed @@ -830,8 +830,8 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) // check BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); - BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); @@ -852,8 +852,8 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) // recheck BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); - BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // publish a new feed to revive the MPA @@ -862,8 +862,8 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) publish_feed( mpa_id, feeder_id, f, feed_icr ); // check - revived - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // Sam tries to update BSRM @@ -883,7 +883,8 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) trx.operations.clear(); trx.operations.push_back( aubop ); PUSH_TX(db, trx, ~0); - BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() + == bsrm_type::global_settlement ); } } @@ -892,8 +893,8 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) // final check BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(3) ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); } catch (fc::exception& e) { @@ -944,8 +945,8 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::individual_settlement_to_fund ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // add a price feed publisher and publish a feed @@ -975,8 +976,8 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) publish_feed( mpa_id, feeder_id, f, feed_icr ); // check - BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( db.find( call2_id ) ); @@ -1001,8 +1002,8 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) // recheck BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::individual_settlement_to_fund ); - BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // Settle debt @@ -1010,8 +1011,8 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) force_settle( borrower2, asset(100000,mpa_id) ); // recheck - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // Sam tries to update BSRM @@ -1042,8 +1043,8 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) // final check BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(3) ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); } catch (fc::exception& e) { @@ -1094,8 +1095,8 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::individual_settlement_to_order ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // add a price feed publisher and publish a feed @@ -1125,8 +1126,8 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) publish_feed( mpa_id, feeder_id, f, feed_icr ); // check - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( db.find( call2_id ) ); @@ -1151,8 +1152,8 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) // recheck BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::individual_settlement_to_order ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( db.find_settled_debt_order(mpa_id) ); // Fill the individual settlement order @@ -1161,8 +1162,8 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) BOOST_CHECK( !sell_ptr ); // recheck - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // Sam tries to update BSRM @@ -1193,8 +1194,8 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) // final check BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(2) ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); } catch (fc::exception& e) { @@ -1252,8 +1253,8 @@ BOOST_AUTO_TEST_CASE( undercollateralized_and_update_bsrm_from_no_settlement ) BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // add a price feed publisher and publish a feed @@ -1283,8 +1284,8 @@ BOOST_AUTO_TEST_CASE( undercollateralized_and_update_bsrm_from_no_settlement ) publish_feed( mpa_id, feeder_id, f, feed_icr ); // check - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( db.find( call_id ) ); BOOST_CHECK( db.find( call2_id ) ); @@ -1307,8 +1308,8 @@ BOOST_AUTO_TEST_CASE( undercollateralized_and_update_bsrm_from_no_settlement ) case bsrm_type::global_settlement: BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); - BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( !db.find( call2_id ) ); @@ -1316,8 +1317,8 @@ BOOST_AUTO_TEST_CASE( undercollateralized_and_update_bsrm_from_no_settlement ) case bsrm_type::individual_settlement_to_fund: BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::individual_settlement_to_fund ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( db.find( call2_id ) ); @@ -1325,8 +1326,8 @@ BOOST_AUTO_TEST_CASE( undercollateralized_and_update_bsrm_from_no_settlement ) case bsrm_type::individual_settlement_to_order: BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::individual_settlement_to_order ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( db.find( call2_id ) ); @@ -1364,11 +1365,21 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) using bsrm_type = bitasset_options::black_swan_response_type; - // Several passes for each BSRM type - for( uint8_t i = 0; i <= 3; ++i ) + // Several passes, for each BSRM type, before and after core-2591 hf + for( uint8_t i = 0; i < 8; ++i ) { - idump( (i) ); + uint8_t bsrm = i % 4; + + idump( (i)(bsrm) ); + if( 4 == i ) + { + // Advance to core-2591 hard fork + generate_blocks(HARDFORK_CORE_2591_TIME); + generate_block(); + } + + set_expiration( db, trx ); ACTORS((sam)(feeder)(borrower)(borrower2)); auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; @@ -1387,7 +1398,7 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; acop.bitasset_opts = bitasset_options(); acop.bitasset_opts->minimum_feeds = 1; - acop.bitasset_opts->extensions.value.black_swan_response_method = i; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm; acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; trx.operations.clear(); @@ -1396,9 +1407,9 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) const asset_object& mpa = db.get(ptx.operation_results[0].get()); asset_id_type mpa_id = mpa.get_id(); - BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == static_cast(i) ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == static_cast(bsrm) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // add a price feed publisher and publish a feed @@ -1431,16 +1442,20 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) const auto& check_result = [&] { BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); - switch( static_cast(i) ) + switch( static_cast(bsrm) ) { case bsrm_type::global_settlement: BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); - BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( !db.find( call2_id ) ); + + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); // can not globally settle again @@ -1449,11 +1464,15 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) case bsrm_type::no_settlement: BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( db.find( call_id ) ); BOOST_CHECK( db.find( call2_id ) ); + + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == price( asset(1250,mpa_id), asset(20) ) ); @@ -1463,8 +1482,8 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) case bsrm_type::individual_settlement_to_fund: BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::individual_settlement_to_fund ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( db.find( call2_id ) ); @@ -1481,11 +1500,16 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) case bsrm_type::individual_settlement_to_order: BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::individual_settlement_to_order ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( db.find( call2_id ) ); + + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 ); + // MSSR = 1250, MCFR = 11, fee = round_down(2000 * 11 / 1250) = 17, fund = 2000 - 17 = 1983 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); @@ -1505,11 +1529,15 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) check_result(); + // publish a new feed (collateral price rises) + f.settlement_price = price( asset(1000,mpa_id), asset(15) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // globally settle - if( bsrm_type::no_settlement == static_cast(i) ) + if( bsrm_type::no_settlement == static_cast(bsrm) ) force_global_settle( mpa_id(db), price( asset(1000,mpa_id), asset(18) ) ); - else if( bsrm_type::individual_settlement_to_fund == static_cast(i) - || bsrm_type::individual_settlement_to_order == static_cast(i) ) + else if( bsrm_type::individual_settlement_to_fund == static_cast(bsrm) + || bsrm_type::individual_settlement_to_order == static_cast(bsrm) ) force_global_settle( mpa_id(db), price( asset(1000,mpa_id), asset(22) ) ); // check @@ -1517,8 +1545,8 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) { BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); - BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( !db.find( call2_id ) ); @@ -1526,12 +1554,14 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); - switch( static_cast(i) ) + switch( static_cast(bsrm) ) { case bsrm_type::global_settlement: break; case bsrm_type::no_settlement: BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).settlement_fund.value, 3600 ); // 1800 * 2 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 ); break; case bsrm_type::individual_settlement_to_fund: BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).settlement_fund.value, 4183 ); // 1983 + 2200 @@ -1540,6 +1570,8 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) break; case bsrm_type::individual_settlement_to_order: BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).settlement_fund.value, 4183 ); // 1983 + 2200 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 ); break; default: BOOST_FAIL( "This should not happen" ); diff --git a/tests/tests/bsrm_indvd_settlement_tests.cpp b/tests/tests/bsrm_indvd_settlement_tests.cpp index 26e567d3ad..fc9f14bf9e 100644 --- a/tests/tests/bsrm_indvd_settlement_tests.cpp +++ b/tests/tests/bsrm_indvd_settlement_tests.cpp @@ -45,18 +45,28 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // multiple passes, - // 0 : individual settlement to order - // 1, 2 : individual settlement to fund - for( int i = 0; i < 3; ++ i ) + // 0 : individual settlement to order, before hf core-2582 + // 1, 2 : individual settlement to fund, before hf core-2582 + // 3 : individual settlement to order, after hf core-2582 + // 4, 5 : individual settlement to fund, after hf core-2582 + // 6 : individual settlement to order, after hf core-2591 + // 7, 8 : individual settlement to fund, after hf core-2591 + for( int i = 0; i < 9; ++ i ) { idump( (i) ); - if( 1 == i ) + if( 3 == i ) { // Advance to core-2582 hard fork generate_blocks(HARDFORK_CORE_2582_TIME); generate_block(); } + else if( 6 == i ) + { + // Advance to core-2591 hard fork + generate_blocks(HARDFORK_CORE_2591_TIME); + generate_block(); + } set_expiration( db, trx ); @@ -72,8 +82,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) fund( borrower5, asset(init_amount) ); using bsrm_type = bitasset_options::black_swan_response_type; - uint8_t bsrm_value = (i == 0) ? static_cast(bsrm_type::individual_settlement_to_order) - : static_cast(bsrm_type::individual_settlement_to_fund); + uint8_t bsrm_value = ( 0 == ( i % 3 ) ) ? static_cast(bsrm_type::individual_settlement_to_order) + : static_cast(bsrm_type::individual_settlement_to_fund); // Create asset asset_create_operation acop; @@ -96,10 +106,10 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) const asset_object& mpa = db.get(ptx.operation_results[0].get()); asset_id_type mpa_id = mpa.get_id(); - if( 0 == i ) + if( 0 == ( i % 3 ) ) BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::individual_settlement_to_order ); - else if( 1 == i || 2 == i ) + else BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::individual_settlement_to_fund ); @@ -119,8 +129,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // borrowers borrow some @@ -305,7 +315,7 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) // fund gets round_up(605 * 1239/1250) = 600, margin call fee = 605 - 600 = 5 // fund debt = 30029 - if( 0 == i ) // to order + if( 0 == ( i % 3 ) ) // to order { // call2 is matched with sell_mid // the size is the same, consider call2 as smaller @@ -349,8 +359,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); const limit_order_object* settled_debt = db.find_settled_debt_order(mpa_id); BOOST_REQUIRE( settled_debt ); @@ -372,7 +382,7 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK_EQUAL( get_balance( seller4_id, mpa_id ), 980000 ); // no change BOOST_CHECK_EQUAL( get_balance( seller4_id, asset_id_type() ), 439 ); // 439 } - else if( 1 == i || 2 == i ) // to fund + else // to fund { // sell_mid price is 100000/2000 = 50 // call pays price is (100000/2000) * (1239:1250) = 49.56 @@ -442,9 +452,9 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) // check BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); - BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 24885 ); @@ -478,7 +488,7 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) check_result(); - if( 1 == i ) // additional tests + if( ( i >= 3 ) && ( 1 == ( i % 3 ) ) ) // additional tests, only pass after hf core-2582 { set_expiration( db, trx ); @@ -504,7 +514,7 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) // reset db.pop_block(); } - else if( 2 == i ) // additional tests + else if( ( i >= 3 ) && ( 2 == ( i % 3 ) ) ) // additional tests. NOTE: infinity loop and OOM before hf core-2582 { set_expiration( db, trx ); @@ -555,7 +565,7 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) } // for i -} FC_CAPTURE_AND_RETHROW() } +} FC_LOG_AND_RETHROW() } /// Tests individual settlement to fund : if disable_force_settle flag is set, /// * able to settle if the fund is not empty, @@ -635,8 +645,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_disable_force_settle_tes BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // borrowers borrow some @@ -669,12 +679,12 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_disable_force_settle_tes f.settlement_price = price( asset(100000,mpa_id), asset(1650,samcoin_id) ); publish_feed( mpa_id, feeder_id, f, feed_icr ); // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485 - // call match price = 100000:1650 * 1000:1239 = 100000:2048.75 = 48.915303153 + // call match price = 100000:1650 * 1000:1239 = 100000:2044.35 = 48.915303153 // check BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // call: margin call fee deducted = round_down(2000*11/1250) = 17, @@ -698,8 +708,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_disable_force_settle_tes BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price.is_null() ); BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); @@ -728,8 +738,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_disable_force_settle_tes BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1785 ); BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 90015 ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); if( 0 == i ) @@ -773,8 +783,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_disable_force_settle_tes BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 ); BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); if( 0 == i ) @@ -814,7 +824,209 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_disable_force_settle_tes } // for i -} FC_CAPTURE_AND_RETHROW() } +} FC_LOG_AND_RETHROW() } + +/// Tests individual settlement to fund : if there is no sufficient price feeds, +/// * before core-2587 hard fork, cannot settle an amount more than the fund, +/// * after core-2587 hard fork, can settle an amount more than the fund: only pay from the fund, no settle order. +BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_no_feed ) +{ try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + { + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::individual_settlement_to_fund); + + // Create asset + asset_id_type samcoin_id = create_user_issued_asset( "SAMCOIN", sam_id(db), charge_market_fee, + price(asset(1, asset_id_type(1)), asset(1)), + 2, 100 ).get_id(); // fee 1% + issue_uia( borrower, asset(init_amount, samcoin_id) ); + issue_uia( borrower2, asset(init_amount, samcoin_id) ); + + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->feed_lifetime_sec = 300; + acop.bitasset_opts->short_backing_asset = samcoin_id; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; + acop.bitasset_opts->extensions.value.force_settle_fee_percent = 300; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.get_id(); + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_fund ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1,samcoin_id) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000, samcoin_id) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->get_id(); + + // undercollateralization price = 100000:2500 * 1250:1000 = 100000:2000 + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2500, samcoin_id) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->get_id(); + + // Transfer funds to sellers + transfer( borrower, seller, asset(100000,mpa_id) ); + transfer( borrower2, seller, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 200000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, samcoin_id ), 0 ); + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(100000,mpa_id), asset(1650,samcoin_id) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485 + // call match price = 100000:1650 * 1000:1239 = 100000:2044.35 = 48.915303153 + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // call: margin call fee deducted = round_down(2000*11/1250) = 17, + // fund receives 2000 - 17 = 1983 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(100000*1239,mpa_id), asset(1983*1000,samcoin_id) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 ); + + // let the feed expire + { + generate_blocks( db.head_block_time() + fc::seconds(350) ); + set_expiration( db, trx ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price.is_null() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 ); + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 ); + } + + // Before core-2587 hard fork, unable to settle more than the fund when no feed + BOOST_CHECK_THROW( force_settle( seller, asset(100001,mpa_id) ), fc::exception ); + + // Advance to core-2587 hard fork + generate_blocks( HARDFORK_CORE_2587_TIME ); + generate_block(); + set_expiration( db, trx ); + + // able to settle more than the fund + auto result = force_settle( seller_id(db), asset(100001,mpa_id) ); + auto op_result = result.get().value; + + auto check_result = [&] + { + // seller gets 1983, market fee 19, finally gets 1964 + // seller pays 100000 + BOOST_CHECK( !op_result.new_objects.valid() ); // no delayed force settlement + BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() ); + BOOST_CHECK( *op_result.paid->begin() == asset( 100000, mpa_id ) ); + BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() ); + BOOST_CHECK( *op_result.received->begin() == asset( 1964, samcoin_id ) ); + BOOST_REQUIRE( op_result.fees.valid() && 1U == op_result.fees->size() ); + BOOST_CHECK( *op_result.fees->begin() == asset( 19, samcoin_id ) ); + // fund is now empty + + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price.is_null() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); // 200000 - 100000 + BOOST_CHECK_EQUAL( get_balance( seller_id, samcoin_id ), 1964 ); + + // Unable to settle when the fund is empty and no feed + BOOST_CHECK_THROW( force_settle( seller, asset(1000,mpa_id) ), fc::exception ); + }; + + check_result(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + check_result(); + + } + +} FC_LOG_AND_RETHROW() } /// Tests individual settlement to fund : settles when price drops, and how taker orders would match after that BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_taking_test ) @@ -896,8 +1108,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_taking_test ) BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // borrowers borrow some @@ -945,12 +1157,12 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_taking_test ) f.settlement_price = price( asset(100000,mpa_id), asset(1650) ); publish_feed( mpa_id, feeder_id, f, feed_icr ); // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485 - // call match price = 100000:1650 * 1000:1239 = 100000:2048.75 = 48.915303153 + // call match price = 100000:1650 * 1000:1239 = 100000:2044.35 = 48.915303153 // check BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // call: margin call fee deducted = round_down(2000*11/1250) = 17, @@ -961,8 +1173,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_taking_test ) BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == price( asset(100000*1239,mpa_id), asset(1983*1000) ) ); BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); - // call pays price = 100000:1983 * 1239:1250 = 49.984871407 - // call match price = 100000:1983 = 50.428643469 + // call pays price (MSSP) = 100000:1983 * 1239:1250 = 49.984871407 + // call match price (MCOP) = 100000:1983 = 50.428643469 BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); @@ -1008,7 +1220,7 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_taking_test ) BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200000 ); BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); - // seller sells more, this order is below MSSP so will not be matched right now + // seller sells more, this order is below MCOP so will not be matched right now limit_ptr = create_sell_order( seller, asset(100000,mpa_id), asset(2000) ); // the limit order is not filled BOOST_REQUIRE( limit_ptr ); @@ -1052,8 +1264,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_taking_test ) // check BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 ); @@ -1132,20 +1344,39 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_taking_test ) } // for i -} FC_CAPTURE_LOG_AND_RETHROW( (0) ) } +} FC_LOG_AND_RETHROW() } -/// Tests individual settlement to order : settles when price drops, and how orders are being matched after settled -BOOST_AUTO_TEST_CASE( individual_settlement_to_order_and_taking_test ) -{ - try { +/// Tests individual settlement to fund: +/// * Before hf core-2591, forced-settlements are filled at individual settlement fund price +/// * After hf core-2591, forced-settlements are filled at margin call order price (MCOP) +BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_taking_price_test ) +{ try { + + // Advance to a recent hard fork + generate_blocks(HARDFORK_CORE_2582_TIME); + generate_block(); + + // multiple passes, + // i == 0 : before hf core-2591, settle less than the amount of debt in fund + // i == 1 : before hf core-2591, settle exactly the amount of debt in fund + // i == 2 : before hf core-2591, settle more than the amount of debt in fund + // i == 3 : after hf core-2591, settle less than the amount of debt in fund + // i == 4 : after hf core-2591, settle exactly the amount of debt in fund + // i == 5 : after hf core-2591, settle more than the amount of debt in fund + for( int i = 0; i < 6; ++ i ) + { + idump( (i) ); + + if( 3 == i ) + { + // Advance to core-2591 hard fork + generate_blocks(HARDFORK_CORE_2591_TIME); + generate_block(); + } - // Advance to core-2467 hard fork - auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_2467_TIME - mi); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); - ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(borrower4)(seller)); + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(borrower4)(borrower5)(seller)(seller2)); auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; fund( sam, asset(init_amount) ); @@ -1154,9 +1385,10 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_order_and_taking_test ) fund( borrower2, asset(init_amount) ); fund( borrower3, asset(init_amount) ); fund( borrower4, asset(init_amount) ); + fund( borrower5, asset(init_amount) ); using bsrm_type = bitasset_options::black_swan_response_type; - uint8_t bsrm_value = static_cast(bsrm_type::individual_settlement_to_order); + uint8_t bsrm_value = static_cast(bsrm_type::individual_settlement_to_fund); // Create asset asset_create_operation acop; @@ -1180,7 +1412,7 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_order_and_taking_test ) asset_id_type mpa_id = mpa.get_id(); BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() - == bsrm_type::individual_settlement_to_order ); + == bsrm_type::individual_settlement_to_fund ); // add a price feed publisher and publish a feed update_feed_producers( mpa_id, { feeder_id } ); @@ -1195,10 +1427,11 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_order_and_taking_test ) publish_feed( mpa_id, feeder_id, f, feed_icr ); - BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // borrowers borrow some @@ -1225,8 +1458,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_order_and_taking_test ) // Transfer funds to sellers transfer( borrower, seller, asset(100000,mpa_id) ); transfer( borrower2, seller, asset(100000,mpa_id) ); - transfer( borrower3, seller, asset(100000,mpa_id) ); - transfer( borrower4, seller, asset(100000,mpa_id) ); + transfer( borrower3, seller2, asset(100000,mpa_id) ); + transfer( borrower4, seller2, asset(100000,mpa_id) ); BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); @@ -1237,28 +1470,35 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_order_and_taking_test ) BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); - BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 400000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 200000 ); BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200000 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); // publish a new feed so that borrower's debt position is undercollateralized f.settlement_price = price( asset(100000,mpa_id), asset(1650) ); publish_feed( mpa_id, feeder_id, f, feed_icr ); // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485 - // call match price = 100000:1650 * 1000:1239 = 100000:2048.75 = 48.915303153 + // call match price = 100000:1650 * 1000:1239 = 100000:2044.35 = 48.915303153 // check - BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); - const limit_order_object* settled_debt = db.find_settled_debt_order(mpa_id); - BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // call: margin call fee deducted = round_down(2000*11/1250) = 17, // fund receives 2000 - 17 = 1983 - BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1983 ); - BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 100000 ); - // order match price = 100000 / 1983 = 50.428643469 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 ); + + BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 17 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(100000*1239,mpa_id), asset(1983*1000) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + // call pays price (MSSP) = 100000:1983 * 1239:1250 = 49.984871407 + // call match price (MCOP) = 100000:1983 = 50.428643469 BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); @@ -1268,173 +1508,1363 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_order_and_taking_test ) BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + // borrower5 is unable to borrow if CR <= real ICR + // for median_feed: 1650 * 1.9 = 3135 + // for current_feed: 1983 * 1.9 / 1.239 = 3040.9 + BOOST_CHECK_THROW( borrow( borrower5, asset(100000, mpa_id), asset(3135) ), fc::exception ); + const call_order_object* call5_ptr = borrow( borrower5, asset(100000, mpa_id), asset(3136) ); + BOOST_REQUIRE( call5_ptr ); + call_order_id_type call5_id = call5_ptr->get_id(); + + BOOST_CHECK_EQUAL( call5_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call5_id(db).collateral.value, 3136 ); + // seller sells some - const limit_order_object* limit_ptr = create_sell_order( seller, asset(10000,mpa_id), asset(100) ); + const limit_order_object* limit_ptr = create_sell_order( seller, asset(80000,mpa_id), asset(100) ); // the limit order is filled BOOST_CHECK( !limit_ptr ); // call2 is partially filled - // limit order gets round_down(10000*(1650/100000)*(1239/1000)) = 204 - // limit order pays round_up(204*(100000/1650)*(1000/1239)) = 9979 - // call2 gets 9979 - // call2 pays round_down(9979*(1650/100000)*(1250/1000)) = 205, margin call fee = 1 + // limit order gets round_down(80000*(1983/100000)) = 1586 + // limit order pays round_up(1586*(100000/1983)) = 79980 + // call2 gets 79980 + // call2 pays round_down(79980*(1983/100000)*(1250/1239)) = 1600, margin call fee = 14 BOOST_CHECK( !db.find( call_id ) ); - BOOST_CHECK_EQUAL( call2_id(db).debt.value, 90021 ); // 100000 - 9979 - BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1895 ); // 2100 - 205 + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 20020 ); // 100000 - 79980 + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 500 ); // 2100 - 1600 + // 20020 / 500 = 40.04 BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + // 100000 / 2200 = 45.454545455 + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 120020 ); // 200000 - 79980 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1586 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200000 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + + BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 31 ); // 17 + 14 + + // seller sells more, this order is below MCOP so will not be matched right now + limit_ptr = create_sell_order( seller, asset(100000,mpa_id), asset(2000) ); + // the limit order is not filled + BOOST_REQUIRE( limit_ptr ); + limit_order_id_type limit_id = limit_ptr->get_id(); + + BOOST_CHECK_EQUAL( limit_ptr->for_sale.value, 100000 ); + + // unable to settle too little amount + BOOST_CHECK_THROW( force_settle( seller2, asset(50,mpa_id) ), fc::exception ); + + // publish a new feed so that current_feed is no longer capped + f.settlement_price = price( asset(100000,mpa_id), asset(1450) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price (MSSP) = 100000:1450 * 1000:1250 = 10000000:181250 = 55.172413793 + // call match price (MCOP) = 100000:1450 * 1000:1239 = 10000000:179655 = 55.662241518 + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + const auto& get_amount_to_settle = [&]() { + switch(i) { + case 0: + case 3: + return 90000; + case 1: + case 4: + return 100000; + case 2: + case 5: + default: + return 110000; + } + }; + + // seller2 settles + share_type amount_to_settle = get_amount_to_settle(); + auto result = force_settle( seller2, asset(amount_to_settle, mpa_id) ); + auto op_result = result.get().value; + + auto check_result = [&] + { + force_settlement_id_type settle_id; + if( 0 == i ) + { + BOOST_CHECK( !op_result.new_objects.valid() ); // force settlement order not created + + // receives = round_down(90000 * 1983 / 100000) = 1784 + // pays = round_up(1784 * 100000 / 1983) = 89965 + // settlement fund = 1983 - 1784 = 199 + // settlement debt = 100000 - 89965 = 10035 + BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() ); + BOOST_CHECK( *op_result.paid->begin() == asset( 89965, mpa_id ) ); + BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() ); + BOOST_CHECK( *op_result.received->begin() == asset( 1784 ) ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 199 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 10035 ); + + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 20020 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 500 ); + // 20020 / 500 = 40.04 + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + // 100000 / 2200 = 45.454545455 + + BOOST_REQUIRE( db.find(limit_id) ); + BOOST_CHECK_EQUAL( limit_id(db).for_sale.value, 100000 ); + + BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 31 ); // 17 + 14 + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 20020 ); // 200000 - 79980 - 100000 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1586 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 110035 ); // 200000 - 89965 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 1784 ); + } + else if( 1 == i ) + { + BOOST_CHECK( !op_result.new_objects.valid() ); // force settlement order not created + + BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() ); + BOOST_CHECK( *op_result.paid->begin() == asset( 100000, mpa_id ) ); + BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() ); + BOOST_CHECK( *op_result.received->begin() == asset( 1983 ) ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 ); + + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 20020 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 500 ); + // 20020 / 500 = 40.04 + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + // 100000 / 2200 = 45.454545455 + + BOOST_REQUIRE( db.find(limit_id) ); + BOOST_CHECK_EQUAL( limit_id(db).for_sale.value, 100000 ); + + BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 31 ); // 17 + 14 + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 20020 ); // 200000 - 79980 - 100000 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1586 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 100000 ); // 200000 - 100000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 1983 ); + } + else if( 2 == i ) + { + // force settlement order created + BOOST_REQUIRE( op_result.new_objects.valid() && 1U == op_result.new_objects->size() ); + settle_id = *op_result.new_objects->begin(); + + BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() ); + BOOST_CHECK( *op_result.paid->begin() == asset( 100000, mpa_id ) ); + BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() ); + BOOST_CHECK( *op_result.received->begin() == asset( 1983 ) ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 ); + + // settle order is matched with call3 + // settle order is smaller + BOOST_CHECK( !db.find(settle_id) ); + // settle order gets round_down((110000-100000) * (1450/100000) * (1239/1000)) = 179 + // settle order pays round_up(179 * (100000/1450) * (1000/1239)) = 9964 + // call3 gets 9964 + // call3 pays round_down(9964 * (1450/100000) * (1250/1000)) = 180, margin call fee = 1 + // call3 is now (100000-9964):(2200-180) = 90036:2020 + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 20020 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 500 ); + // 20020 / 500 = 40.04 + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 90036 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2020 ); + // 90036 / 2020 = 44.572277228 + + BOOST_REQUIRE( db.find(limit_id) ); + BOOST_CHECK_EQUAL( limit_id(db).for_sale.value, 100000 ); + + BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 32 ); // 17 + 14 + 1 + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 20020 ); // 200000 - 79980 - 100000 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1586 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 90036 ); // 200000 - 100000 - 9964 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 2162 ); // 1983 + 179 + + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2200 ); + BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 ); + BOOST_CHECK_EQUAL( get_balance( borrower5_id, asset_id_type() ), init_amount - 3136 ); + } + else if( 3 == i ) + { + BOOST_CHECK( !op_result.new_objects.valid() ); // force settlement order not created + + // settlement fund pays = round_down(90000 * 1983 / 100000) = 1784 + // seller2 pays = round_up(1784 * 100000 / 1983) = 89965 + // settlement fund = 1983 - 1784 = 199 + // settlement debt = 100000 - 89965 = 10035 + // seller2 would receive = round_up(89965 * 179655 / 10000000 ) = 1617 (<1784, so ok) + // collateral fee = 1784 - 1617 = 167 + BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() ); + BOOST_CHECK( *op_result.paid->begin() == asset( 89965, mpa_id ) ); + BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() ); + BOOST_CHECK( *op_result.received->begin() == asset( 1617 ) ); + BOOST_REQUIRE( op_result.fees.valid() && 2U == op_result.fees->size() ); + BOOST_CHECK( *op_result.fees->begin() == asset( 167 ) ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 199 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 10035 ); + + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 20020 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 500 ); + // 20020 / 500 = 40.04 + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + // 100000 / 2200 = 45.454545455 + + BOOST_REQUIRE( db.find(limit_id) ); + BOOST_CHECK_EQUAL( limit_id(db).for_sale.value, 100000 ); + + BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 198 ); // 17 + 14 + 167 + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 20020 ); // 200000 - 79980 - 100000 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1586 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 110035 ); // 200000 - 89965 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 1617 ); + } + else if( 4 == i ) + { + BOOST_CHECK( !op_result.new_objects.valid() ); // force settlement order not created + + // settlement fund pays = 1983 + // seller2 pays = 100000 + // settlement fund = 0 + // settlement debt = 0 + // seller2 would receive = round_up(100000 * 179655 / 10000000 ) = 1797 (<1983, so ok) + // collateral fee = 1983 - 1797 = 186 + BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() ); + BOOST_CHECK( *op_result.paid->begin() == asset( 100000, mpa_id ) ); + BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() ); + BOOST_CHECK( *op_result.received->begin() == asset( 1797 ) ); + BOOST_REQUIRE( op_result.fees.valid() && 2U == op_result.fees->size() ); + BOOST_CHECK( *op_result.fees->begin() == asset( 186 ) ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 ); + + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 20020 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 500 ); + // 20020 / 500 = 40.04 + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + // 100000 / 2200 = 45.454545455 + + BOOST_REQUIRE( db.find(limit_id) ); + BOOST_CHECK_EQUAL( limit_id(db).for_sale.value, 100000 ); + + BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 217 ); // 17 + 14 + 186 + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 20020 ); // 200000 - 79980 - 100000 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1586 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 100000 ); // 200000 - 100000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 1797 ); + } + else if( 5 == i ) + { + // force settlement order created + BOOST_REQUIRE( op_result.new_objects.valid() && 1U == op_result.new_objects->size() ); + settle_id = *op_result.new_objects->begin(); + + // settlement fund pays = 1983 + // seller2 pays = 100000 + // settlement fund = 0 + // settlement debt = 0 + // seller2 would receive = round_up(100000 * 179655 / 10000000 ) = 1797 (<1983, so ok) + // collateral fee = 1983 - 1797 = 186 + BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() ); + BOOST_CHECK( *op_result.paid->begin() == asset( 100000, mpa_id ) ); + BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() ); + BOOST_CHECK( *op_result.received->begin() == asset( 1797 ) ); + BOOST_REQUIRE( op_result.fees.valid() && 2U == op_result.fees->size() ); + BOOST_CHECK( *op_result.fees->begin() == asset( 186 ) ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 ); + + // settle order is matched with call3 + // settle order is smaller + BOOST_CHECK( !db.find(settle_id) ); + // settle order gets round_down((110000-100000) * (1450/100000) * (1239/1000)) = 179 + // settle order pays round_up(179 * (100000/1450) * (1000/1239)) = 9964 + // call3 gets 9964 + // call3 pays round_down(9964 * (1450/100000) * (1250/1000)) = 180, margin call fee = 1 + // call3 is now (100000-9964):(2200-180) = 90036:2020 + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 20020 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 500 ); + // 20020 / 500 = 40.04 + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 90036 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2020 ); + // 90036 / 2020 = 44.572277228 + + BOOST_REQUIRE( db.find(limit_id) ); + BOOST_CHECK_EQUAL( limit_id(db).for_sale.value, 100000 ); + + BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 218 ); // 17 + 14 + 186 + 1 + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 20020 ); // 200000 - 79980 - 100000 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1586 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 90036 ); // 200000 - 100000 - 9964 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 1976 ); // 1797 + 179 + + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2200 ); + BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 ); + BOOST_CHECK_EQUAL( get_balance( borrower5_id, asset_id_type() ), init_amount - 3136 ); + } + + }; + + check_result(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + check_result(); + + // reset + db.pop_block(); + + } // for i + +} FC_LOG_AND_RETHROW() } + +/// Tests individual settlement to order : settles when price drops, and the settled-debt order is matched as maker +/// * Before hf core-2591, the settled-debt order is filled at its own price (collateral amount / debt amount) +/// * After hf core-2591, the settled-debt order is filled at margin call order price (MCOP) +BOOST_AUTO_TEST_CASE( individual_settlement_to_order_and_matching_as_maker_test ) +{ try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + // multiple passes, + // i == 0 : before hf core-2591 + // i == 1 : after hf core-2591 + for( int i = 0; i < 2; ++i ) + { + idump( (i) ); + + if( 1 == i ) + { + // Advance to core-2591 hard fork + generate_blocks(HARDFORK_CORE_2591_TIME); + generate_block(); + } + + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(borrower4)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + fund( borrower4, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::individual_settlement_to_order); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.get_id(); + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_order ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->get_id(); + + // undercollateralization price = 100000:2100 * 1250:1000 = 100000:1680 + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->get_id(); + + // undercollateralization price = 100000:2200 * 1250:1000 = 100000:1760 + const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(2200) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->get_id(); + + // undercollateralization price = 100000:2500 * 1250:1000 = 100000:2000 + const call_order_object* call4_ptr = borrow( borrower4, asset(100000, mpa_id), asset(2500) ); + BOOST_REQUIRE( call4_ptr ); + call_order_id_type call4_id = call4_ptr->get_id(); + + // Transfer funds to sellers + transfer( borrower, seller, asset(100000,mpa_id) ); + transfer( borrower2, seller, asset(100000,mpa_id) ); + transfer( borrower3, seller, asset(100000,mpa_id) ); + transfer( borrower4, seller, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 400000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(100000,mpa_id), asset(1650) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485 + // call match price = 100000:1650 * 1000:1239 = 100000:2044.35 = 48.915303153 + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_individually_settled_to_fund() ); + const limit_order_object* settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + + // call: margin call fee deducted = round_down(2000*11/1250) = 17, + // fund receives 2000 - 17 = 1983 + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1983 ); + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 100000 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); + BOOST_CHECK( settled_debt->sell_price == asset(1983)/asset(100000,mpa_id) ); + // order match price = 100000 / 1983 = 50.428643469 + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 17 ); + + // seller sells some + const limit_order_object* limit_ptr = create_sell_order( seller, asset(10000,mpa_id), asset(100) ); + // the limit order is filled + BOOST_CHECK( !limit_ptr ); + + // call2 is partially filled + // limit order gets round_down(10000*(1650/100000)*(1239/1000)) = 204 + // limit order pays round_up(204*(100000/1650)*(1000/1239)) = 9979 + // call2 gets 9979 + // call2 pays round_down(9979*(1650/100000)*(1250/1000)) = 205, margin call fee = 1 + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 90021 ); // 100000 - 9979 + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1895 ); // 2100 - 205 + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + // no change to the settled-debt order + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1983 ); + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 100000 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); + BOOST_CHECK( settled_debt->sell_price == asset(1983)/asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 390021 ); // 400000 - 9979 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 204 ); + + BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 18 ); // 17 + 1 + + // publish a new feed so that 2 other debt positions are undercollateralized + f.settlement_price = price( asset(100000,mpa_id), asset(1800) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 100000:1800 * 1000:1250 = 100000:2250 = 44.444444444 + // call match price = 100000:1800 * 1000:1239 = 100000:2230.2 = 44.83902789 + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_individually_settled_to_fund() ); + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !db.find( call3_id ) ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + // call2: margin call fee deducted = round_down(1895*11/1250) = 16, + // fund receives 1895 - 16 = 1879 + // call3: margin call fee deducted = round_down(2200*11/1250) = 19, + // fund receives 2200 - 19 = 2181 + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 6043 ); // 1983 + 1879 + 2181 + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290021 ); // 100000 + 90021 + 100000 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 290021 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 6043 ); + BOOST_CHECK( settled_debt->sell_price == asset(6043)/asset(290021,mpa_id) ); + // order match price = 290021 / 6043 = 47.992884329 + + BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 53 ); // 17 + 1 + 16 + 19 + + // borrower buys at higher price + const limit_order_object* buy_high = create_sell_order( borrower, asset(10), asset(100,mpa_id) ); + BOOST_CHECK( buy_high ); + limit_order_id_type buy_high_id = buy_high->get_id(); + + // seller sells some, this will match buy_high, + // and when it matches call4, it will be cancelled since it is too small + limit_ptr = create_sell_order( seller, asset(120,mpa_id), asset(1) ); + // the limit order is filled + BOOST_CHECK( !limit_ptr ); + // buy_high is filled + BOOST_CHECK( !db.find( buy_high_id ) ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); - BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1983 ); - BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 100000 ); + // no change to the settled-debt order + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 6043 ); + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290021 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 290021 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 6043 ); + BOOST_CHECK( settled_debt->sell_price == asset(6043)/asset(290021,mpa_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389921 ); // 400000 - 9979 - 100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 214 ); // 204 + 10 + + // publish a new feed so that + // * before hf core-2591, the settled debt order is in the front of the order book + // * after hf core-2591, the settled debt order is updated to be behind the margin call orders + f.settlement_price = price( asset(100000,mpa_id), asset(1600) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 100000:1600 * 1000:1250 = 100000:2000 = 50 + // call match price = 100000:1600 * 1000:1239 = 100000:1982.4 = 50.443906376 + + if( 0 == i ) + { + // no change to the settled-debt order + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 6043 ); + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290021 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 290021 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 6043 ); + BOOST_CHECK( settled_debt->sell_price == asset(6043)/asset(290021,mpa_id) ); + } + else if( 1 == i ) + { + // the settled-debt order is updated + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 5750 ); // round_up(290021 * 19824 / 1000000) + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290052 ); //round_down(5750*1000000/19824) + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 290021 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 6043 ); + BOOST_CHECK( settled_debt->sell_price == asset(19824)/asset(1000000,mpa_id) ); + } + + // borrower buys at higher price + buy_high = create_sell_order( borrower, asset(10), asset(100,mpa_id) ); + BOOST_CHECK( buy_high ); + buy_high_id = buy_high->get_id(); + + // seller sells some, this will match buy_high, then + // * before hf core-2591, when it matches the settled debt, it will be cancelled since it is too small + // * after hf core-2591, when it matches a call order, it will be cancelled since it is too small + limit_ptr = create_sell_order( seller, asset(120,mpa_id), asset(1) ); + // the limit order is filled + BOOST_CHECK( !limit_ptr ); + // buy_high is filled + BOOST_CHECK( !db.find( buy_high_id ) ); + + if( 0 == i ) + { + // no change to the settled-debt order + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 6043 ); + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290021 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 290021 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 6043 ); + BOOST_CHECK( settled_debt->sell_price == asset(6043)/asset(290021,mpa_id) ); + } + else if( 1 == i ) + { + // no change to the settled-debt order + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 5750 ); + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290052 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 290021 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 6043 ); + BOOST_CHECK( settled_debt->sell_price == asset(19824)/asset(1000000,mpa_id) ); + } + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389821 ); // 400000 - 9979 - 100 - 100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 224 ); // 204 + 10 + 10 + + BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 53 ); // 17 + 1 + 16 + 19 + + // seller sells some + limit_ptr = create_sell_order( seller, asset(10000,mpa_id), asset(100) ); + // the limit order is filled + BOOST_CHECK( !limit_ptr ); + + if( 0 == i ) + { + // the settled debt is partially filled + // limit order receives = round_down(10000*6043/290021) = 208 + // settled debt receives = round_up(208*290021/6043) = 9983 + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !db.find( call3_id ) ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 5835 ); // 6043 - 208 + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 280038 ); // 290021 - 9983 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 280038 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 5835 ); + BOOST_CHECK( settled_debt->sell_price == asset(5835)/asset(280038,mpa_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 379838 ); // 400000 - 9979 - 100 - 100 - 9983 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 432 ); // 204 + 10 + 10 + 208 + + BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 53 ); // no change + } + else if( 1 == i ) + { + // call4 is partially filled + // limit order gets round_down(10000*(1600/100000)*(1239/1000)) = 198 + // limit order pays round_up(198*(100000/1600)*(1000/1239)) = 9988 + // call4 gets 9988 + // call4 pays round_down(9988*(1600/100000)*(1250/1000)) = 199, margin call fee = 1 + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !db.find( call3_id ) ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 90012 ); // 100000 - 9988 + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2301 ); // 2500 - 199 + + // no change to the settled-debt order + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 5750 ); + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290052 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 290021 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 6043 ); + BOOST_CHECK( settled_debt->sell_price == asset(19824)/asset(1000000,mpa_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 379833 ); // 400000 - 9979 - 100 - 100 - 9988 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 422 ); // 204 + 10 + 10 + 198 + + BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 54 ); // 53 + 1 + } + + // seller sells some + limit_ptr = create_sell_order( seller, asset(300000,mpa_id), asset(3000) ); + // the limit order is filled + BOOST_CHECK( !limit_ptr ); + + auto check_result = [&] + { + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + + if( 0 == i ) + { + // the settled debt is fully filled + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + // limit order reminder = 300000 - 280038 = 19962 + // call4 is partially filled + // limit order gets round_down(19962*(1600/100000)*(1239/1000)) = 395 + // limit order pays round_up(395*(100000/1600)*(1000/1239)) = 19926 + // call4 gets 19926 + // call4 pays round_down(19926*(1600/100000)*(1250/1000)) = 398, margin call fee = 3 + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 79874 ); // 400000 - 9979 - 100 - 100 - 9983 + // - 280038 - 19926 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 6662 ); // 204 + 10 + 10 + 208 + 5835 + 395 + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !db.find( call3_id ) ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 80074 ); // 100000 - 19926 + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2102 ); // 2500 - 398 + + BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 56 ); // 53 + 3 + } + else if( 1 == i ) + { + // call4 is fully filled + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !db.find( call3_id ) ); + BOOST_CHECK( !db.find( call4_id ) ); + // call4 gets 90012 + // limit order gets round_up(90012*(1600/100000)*(1239/1000)) = 1785 + // call4 pays round_up(90012*(1600/100000)*(1250/1000)) = 1801, margin call fee = 1801 - 1785 = 16 + + // limit order reminder = 300000 - 90012 = 209988 + // the settled debt is partially filled + // limit order receives = round_down(209988*19824/1000000) = 4162 + // settled debt receives = round_up(4162*1000000/19824) = 209948 + // settled debt pays = round_down(209948*6043/290021) = 4374, collateral fee = 4374 - 4162 = 212 + + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1588 ); // round_up( 80073 * 19824 / 1000000 ) + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 80104 ); //rnd_down(1588*1000000/19824) + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 80073 ); // 290021 + // - 209948 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1669 ); // 6043 - 4374 + BOOST_CHECK( settled_debt->sell_price == asset(19824)/asset(1000000,mpa_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 79873 ); // 400000 - 9979 - 100 - 100 - 9988 + // - 90012 - 209948 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 6369 ); // 204 + 10 + 10 + 198 + 1785 + 4162 + + BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 282 ); // 54 + 16 + 212 + } + + }; + + check_result(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + check_result(); + + if( 1 == i ) + { + + // undercollateralization price = 100000:5000 * 1250:1000 = 100000:4000 + const call_order_object* call5_ptr = borrow( borrower4_id(db), asset(100000, mpa_id), asset(5000) ); + BOOST_REQUIRE( call5_ptr ); + call_order_id_type call5_id = call5_ptr->get_id(); + + BOOST_CHECK_EQUAL( call5_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call5_id(db).collateral.value, 5000 ); + + transfer( borrower4_id(db), seller_id(db), asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 179873 ); // 79873 + 100000 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 6369 ); // no change + + // seller sells some + limit_ptr = create_sell_order( seller_id(db), asset(100000,mpa_id), asset(1000) ); + // the limit order is partially filled + BOOST_REQUIRE( limit_ptr ); + limit_order_id_type limit_id = limit_ptr->get_id(); + + auto check_result_1 = [&] + { + // the settled-debt order is fully filled + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // settled debt receives = 80073 + // limit order receives = round_up(80073*19824/1000000) = 1588 + // settled debt pays = 1669, collateral fee = 1669 - 1588 = 81 + + BOOST_CHECK_EQUAL( limit_id(db).for_sale.value, 19927 ); // 100000 - 80073 + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 79873 ); // 179873 - 100000 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 7957 ); // 6369 + 1588 + + BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 363 ); // 282 + 81 + }; + + check_result_1(); + + BOOST_TEST_MESSAGE( "Generate a new block" ); + generate_block(); + + check_result_1(); + + // reset + db.pop_block(); + } + + // reset + db.pop_block(); + + } // for i + +} FC_LOG_AND_RETHROW() } + +/// Tests individual settlement to order : +/// after hf core-2591, the settled-debt order is matched as taker when price feed is updated +BOOST_AUTO_TEST_CASE( individual_settlement_to_order_and_matching_as_taker_test ) +{ try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + // multiple passes, + // i == 0 : before hf core-2591 + // i >= 1 : after hf core-2591 + for( int i = 0; i < 6; ++i ) + { + idump( (i) ); + + if( 1 == i ) + { + // Advance to core-2591 hard fork + generate_blocks(HARDFORK_CORE_2591_TIME); + generate_block(); + } + + set_expiration( db, trx ); - BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 390021 ); // 400000 - 9979 - BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 204 ); + ACTORS((sam)(feeder)(borrower)(borrower2)(seller)(seller2)); - // publish a new feed so that 2 other debt positions are undercollateralized - f.settlement_price = price( asset(100000,mpa_id), asset(1800) ); - publish_feed( mpa_id, feeder_id, f, feed_icr ); - // call pays price = 100000:1800 * 1000:1250 = 100000:2250 = 44.444444444 - // call match price = 100000:1800 * 1000:1239 = 100000:2230.2 = 44.83902789 + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); - BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::individual_settlement_to_order); - BOOST_CHECK( !db.find( call_id ) ); - BOOST_CHECK( !db.find( call2_id ) ); - BOOST_CHECK( !db.find( call3_id ) ); - BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); - BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; - // call2: margin call fee deducted = round_down(1895*11/1250) = 16, - // fund receives 1895 - 16 = 1879 - // call3: margin call fee deducted = round_down(2200*11/1250) = 19, - // fund receives 2200 - 19 = 2181 - BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 6043 ); // 1983 + 1879 + 2181 - BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290021 ); // 100000 + 90021 + 100000 - // order match price = 290021 / 6043 = 47.992884329 + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.get_id(); - // borrower buys at higher price - const limit_order_object* buy_high = create_sell_order( borrower, asset(10), asset(100,mpa_id) ); - BOOST_CHECK( buy_high ); - limit_order_id_type buy_high_id = buy_high->get_id(); + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_order ); - // seller sells some, this will match buy_high, - // and when it matches call4, it will be cancelled since it is too small - limit_ptr = create_sell_order( seller, asset(120,mpa_id), asset(1) ); - // the limit order is filled - BOOST_CHECK( !limit_ptr ); - // buy_high is filled - BOOST_CHECK( !db.find( buy_high_id ) ); + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); - BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); - BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; - BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 6043 ); // no change - BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290021 ); // no change + uint16_t feed_icr = 1900; - BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389921 ); // 400000 - 9979 - 100 - BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 214 ); // 204 + 10 + publish_feed( mpa_id, feeder_id, f, feed_icr ); - // publish a new feed so that the settled debt order is in the front of the order book - f.settlement_price = price( asset(100000,mpa_id), asset(1600) ); + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->get_id(); + + // Transfer funds to sellers + transfer( borrower, seller, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(100000,mpa_id), asset(1650) ); publish_feed( mpa_id, feeder_id, f, feed_icr ); - // call pays price = 100000:1600 * 1000:1250 = 100000:2000 = 50 - // call match price = 100000:1600 * 1000:1239 = 100000:1982.4 = 50.443906376 + // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485 + // call match price = 100000:1650 * 1000:1239 = 100000:2044.35 = 48.915303153 - // borrower buys at higher price - buy_high = create_sell_order( borrower, asset(10), asset(100,mpa_id) ); - BOOST_CHECK( buy_high ); - buy_high_id = buy_high->get_id(); + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_individually_settled_to_fund() ); + const limit_order_object* settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); - // seller sells some, this will match buy_high, - // and when it matches the settled debt, it will be cancelled since it is too small - limit_ptr = create_sell_order( seller, asset(120,mpa_id), asset(1) ); - // the limit order is filled - BOOST_CHECK( !limit_ptr ); - // buy_high is filled - BOOST_CHECK( !db.find( buy_high_id ) ); + // call: margin call fee deducted = round_down(2000*11/1250) = 17, + // fund receives 2000 - 17 = 1983 + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1983 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); + BOOST_CHECK( settled_debt->sell_price == asset(1983)/asset(100000,mpa_id) ); + // order match price = 100000 / 1983 = 50.428643469 - BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 6043 ); // no change - BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290021 ); // no change + BOOST_CHECK( !db.find( call_id ) ); - BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389821 ); // 400000 - 9979 - 100 - 100 - BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 224 ); // 204 + 10 + 10 + BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 17 ); // seller sells some - limit_ptr = create_sell_order( seller, asset(10000,mpa_id), asset(100) ); + const limit_order_object* limit_ptr = create_sell_order( seller, asset(10000,mpa_id), asset(100) ); // the limit order is filled BOOST_CHECK( !limit_ptr ); + // the settled debt is partially filled - // limit order receives = round_down(10000*6043/290021) = 208 - // settled debt receives = round_up(208*290021/6043) = 9983 + // limit order receives = round_down(10000*1983/100000) = 198 + // settled debt receives = round_up(198*100000/1983) = 9985 + // settled debt pays = 198, collateral fee = 0 - BOOST_CHECK( !db.find( call_id ) ); - BOOST_CHECK( !db.find( call2_id ) ); - BOOST_CHECK( !db.find( call3_id ) ); - BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); - BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1785 ); // 1983 - 198 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 90015 ); // 100000 - 9985 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1785 ); + if( 0 == i ) + BOOST_CHECK( settled_debt->sell_price == asset(1785)/asset(90015,mpa_id) ); + else + BOOST_CHECK( settled_debt->sell_price == asset(1983)/asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90015 ); // 100000 - 9985 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 198 ); - BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 5835 ); // 6043 - 208 - BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 280038 ); // 290021 - 9983 + BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 17 ); + + // publish a new feed (collateral price rises) + f.settlement_price = price( asset(200,mpa_id), asset(1) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 200:1 * 1000:1250 = 200000:1250 = 160 + // call match price = 200:1 * 1000:1239 = 200000:1239 = 161.420500404 - BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 379838 ); // 400000 - 9979 - 100 - 100 - 9983 - BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 432 ); // 204 + 10 + 10 + 208 + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( settled_debt->is_settled_debt ); + if( 0 == i ) + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1785 ); + else + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 558 ); // round_up( 90015 * 1239 / 200000 ) + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 90015 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1785 ); + if( 0 == i ) + BOOST_CHECK( settled_debt->sell_price == asset(1785)/asset(90015,mpa_id) ); + else + BOOST_CHECK( settled_debt->sell_price == asset(1239)/asset(200000,mpa_id) ); // seller sells some - limit_ptr = create_sell_order( seller, asset(300000,mpa_id), asset(3000) ); - // the limit order is filled - BOOST_CHECK( !limit_ptr ); + limit_ptr = create_sell_order( seller, asset(10000,mpa_id), asset(150) ); + if( 0 == i ) + { + // the limit order is filled + BOOST_CHECK( !limit_ptr ); + + // the settled debt is partially filled + // limit order receives = round_down(10000*1785/90015) = 198 + // settled debt receives = round_up(198*90015/1785) = 9985 + // settled debt pays = 198, collateral fee = 0 + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1587 ); // 1983 - 198 - 198 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 80030 ); // 100000 - 9985 + // - 9985 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1587 ); + BOOST_CHECK( settled_debt->sell_price == asset(1587)/asset(80030,mpa_id) ); + + BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 17 ); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + // reset + db.pop_block(); + // this branch ends here + continue; + } + + // the limit order is not filled + BOOST_REQUIRE( limit_ptr ); + limit_order_id_type limit_id = limit_ptr->get_id(); + + BOOST_CHECK_EQUAL( limit_id(db).for_sale.value, 10000 ); + + // the settled-debt order is unchanged + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 558 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 90015 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1785 ); + BOOST_CHECK( settled_debt->sell_price == asset(1239)/asset(200000,mpa_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 80015 ); // 100000 - 9985 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 198 ); + + BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 17 ); + + call_order_id_type call2_id; + limit_order_id_type limit2_id; + if( 1 == i ) + { + // do nothing here so that there is no call order exists + // so the settled-debt order will match the limit order on the next price feed update + } + if( 2 == i ) + { + // create a small call order that will go undercollateralized on the next price feed update + // so the settled-debt order after merged the new call order will still be well collateralized + // and will match the limit order + // undercollateralization price = 10000:100 * 1250:1000 = 100000:800 + const call_order_object* call2_ptr = borrow( borrower2, asset(10000, mpa_id), asset(100) ); + BOOST_REQUIRE( call2_ptr ); + call2_id = call2_ptr->get_id(); + + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 10000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 100 ); + } + else if( 3 == i ) + { + // create a huge call order that will go undercollateralized on the next price feed update + // so the settled-debt order after merged the new call order will be undercollateralized too + // and will not match the limit order + // undercollateralization price = 1000000:10000 * 1250:1000 = 100000:800 + const call_order_object* call2_ptr = borrow( borrower2, asset(1000000, mpa_id), asset(10000) ); + BOOST_REQUIRE( call2_ptr ); + call2_id = call2_ptr->get_id(); + + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 1000000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 10000 ); + } + else if( 4 == i ) + { + // create a big call order that will be margin called on the next price feed update + // so the settled-debt order will have no limit order to match with + // undercollateralization price = 100000:2400 * 1250:1000 = 100000:1920 + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2400) ); + BOOST_REQUIRE( call2_ptr ); + call2_id = call2_ptr->get_id(); + + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2400 ); + } + else if( 5 == i ) + { + // create a big call order that will not be margin called on the next price feed update + // so the settled-debt order will match the limit order + // undercollateralization price = 100000:5000 * 1250:1000 = 100000:4000 + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(5000) ); + BOOST_REQUIRE( call2_ptr ); + call2_id = call2_ptr->get_id(); + + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 5000 ); + + // Transfer funds to sellers + transfer( borrower2, seller2, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + + // seller2 sells some + const limit_order_object* limit2_ptr = create_sell_order( seller2, asset(100000,mpa_id), asset(1550) ); + BOOST_REQUIRE( limit2_ptr ); + limit2_id = limit2_ptr->get_id(); - auto final_check = [&] + BOOST_CHECK_EQUAL( limit2_id(db).for_sale.value, 100000 ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 0 ); // 100000 - 100000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + } + + // publish a new feed (collateral price drops) + f.settlement_price = price( asset(100000,mpa_id), asset(1350) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price (MSSP) = 100000:1350 * 1000:1250 = 100000:1687.5 = 59.259259259 + // call match price (MCOP) = 100000:1350 * 1000:1239 = 100000:1672.65 = 59.78537052 + + auto check_result = [&] { BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); - // the settled debt is fully filled - BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); - // limit order reminder = 300000 - 280038 = 19962 - // call4 is partially filled - // limit order gets round_down(19962*(1600/100000)*(1239/1000)) = 395 - // limit order pays round_up(395*(100000/1600)*(1000/1239)) = 19926 - // call4 gets 19926 - // call4 pays round_down(19926*(1600/100000)*(1250/1000)) = 398, margin call fee = 3 + // the settled-debt order was: + // settled_debt_amount = 90015 + // settled_collateral_amount = 1785 - BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 79874 ); // 400000 - 9979 - 100 - 100 - 9983 - // - 280038 - 19926 - BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 6662 ); // 204 + 10 + 10 + 208 + 5835 + 395 + // the limit order was selling 10000 MPA for 150 CORE - BOOST_CHECK( !db.find( call_id ) ); - BOOST_CHECK( !db.find( call2_id ) ); - BOOST_CHECK( !db.find( call3_id ) ); - BOOST_CHECK_EQUAL( call4_id(db).debt.value, 80074 ); // 100000 - 19926 - BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2102 ); // 2500 - 398 + if( 1 == i ) + { + // the settled-debt order is matched with the limit order + // the limit order is fully filled + BOOST_CHECK( !db.find( limit_id ) ); + + // the settled-debt order is partially filled, match price is 10000:150 + // limit order receives = 150 + // settled debt receives = 10000 + // settled debt pays = round_down(10000*1785/90015) = 198, collateral fee = 198 - 150 = 48 + + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1339 ); // round_up( 80015 * 167265 / 10000000 ) + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 80015 ); //90015 - 10000 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1587 ); // 1785 - 198 + BOOST_CHECK( settled_debt->sell_price == asset(167265)/asset(10000000,mpa_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 80015 ); // 100000 - 9985 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 348 ); // 198 + 150 + + BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 65 ); // 17 + 48 + } + else if( 2 == i ) + { + // call2 is individually settled + BOOST_CHECK( !db.find( call2_id ) ); + + // margin call fee deducted = round_down(100*11/1250) = 0, + // fund receives 100, collateral = 1785 + 100 = 1885 + // fund debt = 90015 + 10000 = 100015 + // fund price = 100015 / 2785 = 53.058355438 < MCOP 59.78537052 + + // the settled-debt order is matched with the limit order + // the limit order is fully filled + BOOST_CHECK( !db.find( limit_id ) ); + + // the settled-debt order is partially filled, match price is 10000:150 + // limit order receives = 150 + // settled debt receives = 10000 + // settled debt pays = round_down(10000*1885/100015) = 188, collateral fee = 188 - 150 = 38 + + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1506 ); // round_up( 90015 * 167265 / 10000000 ) + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 90015 ); //90015 - 10000 + // + 10000 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1697 ); // 1785 + 100 + // - 188 + BOOST_CHECK( settled_debt->sell_price == asset(167265)/asset(10000000,mpa_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 80015 ); // 100000 - 9985 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 348 ); // 198 + 150 + + BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 55 ); // 17 + 38 + } + else if( 3 == i ) + { + // call2 is individually settled + BOOST_CHECK( !db.find( call2_id ) ); + + // margin call fee deducted = round_down(10000*11/1250) = 88, + // fund receives 10000 - 88 = 9912, collateral = 1785 + 9912 = 11697 + // fund debt = 90015 + 1000000 = 1090015 + // fund price = 1090015 / 11697 = 93.187569462 > MCOP 59.78537052 + + // the settled-debt order can't be matched with the limit order + BOOST_REQUIRE( db.find( limit_id ) ); + BOOST_CHECK_EQUAL( limit_id(db).for_sale.value, 10000 ); // no change + + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 11697 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 1090015 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 11697 ); + BOOST_CHECK( settled_debt->sell_price == asset(11697)/asset(1090015,mpa_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 80015 ); // no change + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 198 ); // no change + + BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 105 ); // 17 + 88 + } + else if( 4 == i ) + { + // call2 is margin called, matched with the limit order + // the limit order is fully filled + BOOST_CHECK( !db.find( limit_id ) ); + + // call2 is partially filled + // limit order receives = 150 + // call2 receives = 10000 + // margin call fee = round_down(150*11/1250) = 1 + // call2 pays 150 + 1 = 151 + + BOOST_REQUIRE( db.find( call2_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 90000 ); // 100000 - 10000 + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2249 ); // 2400 - 151 + + // the settled-debt order is not matched + + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + BOOST_CHECK( settled_debt->is_settled_debt ); + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1506 ); // round_up( 90015 * 167265 / 10000000 ) + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 90015 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1785 ); + BOOST_CHECK( settled_debt->sell_price == asset(167265)/asset(10000000,mpa_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 80015 ); // 100000 - 9985 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 348 ); // 198 + 150 + + BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 18 ); // 17 + 1 + } + else if( 5 == i ) + { + // call2 is unchanged + BOOST_REQUIRE( db.find( call2_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 5000 ); + + // the settled-debt order is matched with the limit order + // the limit order is fully filled + BOOST_CHECK( !db.find( limit_id ) ); + + // the settled-debt order is partially filled, match price is 10000:150 + // limit order receives = 150 + // settled debt receives = 10000, settled_debt = 90015 - 10000 = 80015 + // settled debt pays = round_down(10000*1785/90015) = 198, collateral fee = 198 - 150 = 48 + // settled_collateral = 1785 - 198 = 1587 + + // then, the settled-debt order is matched with limit2 + // the settled-debt order is fully filled, match price is 10000:155 + // settled debt receives = 80015 + // limit2 receives = round_up(80015*155/10000) = 1241 + // settled debt pays = 1587, collateral fee = 1587 - 1241 = 346 + + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + BOOST_CHECK_EQUAL( limit2_id(db).for_sale.value, 19985 ); // 100000 - 80015 + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 80015 ); // 100000 - 9985 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 348 ); // 198 + 150 + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 0 ); // 100000 - 100000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 1241 ); + + BOOST_CHECK_EQUAL( mpa_id(db).dynamic_data(db).accumulated_collateral_fees.value, 411 ); // 17 + 48 + 346 + } }; - final_check(); + check_result(); BOOST_TEST_MESSAGE( "Generate a block" ); generate_block(); - final_check(); + check_result(); + + // reset + db.pop_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + } // for i + +} FC_LOG_AND_RETHROW() } /// Tests a scenario that force settlements get cancelled on expiration when there is no debt position /// due to individual settlement to order BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_no_debt_position ) -{ - try { +{ try { // Advance to core-2467 hard fork auto mi = db.get_global_properties().parameters.maintenance_interval; @@ -1510,8 +2940,8 @@ BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_no_debt_position ) BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_individually_settled_to_fund() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // borrowers borrow some @@ -1542,13 +2972,13 @@ BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_no_debt_position ) f.settlement_price = price( asset(100000,mpa_id), asset(1650) ); publish_feed( mpa_id, feeder_id, f, feed_icr ); // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485 - // call match price = 100000:1650 * 1000:1239 = 100000:2048.75 = 48.915303153 + // call match price = 100000:1650 * 1000:1239 = 100000:2044.35 = 48.915303153 // check BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); const limit_order_object* settled_debt = db.find_settled_debt_order(mpa_id); BOOST_REQUIRE( settled_debt ); @@ -1585,8 +3015,8 @@ BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_no_debt_position ) // check BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); settled_debt = db.find_settled_debt_order(mpa_id); BOOST_REQUIRE( settled_debt ); @@ -1604,10 +3034,6 @@ BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_no_debt_position ) BOOST_CHECK_EQUAL( get_balance( seller_id, mpa2_id ), 88900 ); // 100000 - 11100 BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/bsrm_no_settlement_tests.cpp b/tests/tests/bsrm_no_settlement_tests.cpp index db7d304da9..46958c54ed 100644 --- a/tests/tests/bsrm_no_settlement_tests.cpp +++ b/tests/tests/bsrm_no_settlement_tests.cpp @@ -112,7 +112,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) // check BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1250,mpa_id), asset(2000) ) ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed // 1000 * (2000/1250) * 1.9 = 3040 @@ -131,7 +131,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) // check BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1250,mpa_id), asset(2100) ) ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); // Sam update MSSR and MCFR // note: borrower's position is undercollateralized again due to the mssr change @@ -151,7 +151,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) BOOST_CHECK_EQUAL( mpa.bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1300u ); BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1300,mpa_id), asset(2100) ) ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); // Transfer funds to sellers transfer( borrower, seller, asset(1000,mpa_id) ); @@ -203,7 +203,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(11557,mpa_id), asset(18670) ) ); // 13:10 * (1000-111):(2100-111*210/100) // 13:10 * 889:1867 - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); @@ -253,7 +253,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(8957,mpa_id), asset(19600) ) ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); BOOST_CHECK_EQUAL( call_id(db).debt.value, 689 ); BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1960 ); @@ -283,7 +283,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( !db.find(call_id) ); BOOST_CHECK( !db.find(call2_id) ); @@ -381,7 +381,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_small_limit_taker_test ) // check BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2000) ) ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed // 100000 * (2000/125000) * 1.9 = 3040 @@ -400,7 +400,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_small_limit_taker_test ) // check BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2100) ) ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); // Sam update MSSR and MCFR // note: borrower's position is undercollateralized again due to the mssr change @@ -420,7 +420,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_small_limit_taker_test ) BOOST_CHECK_EQUAL( mpa.bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1300u ); BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(130000,mpa_id), asset(2100) ) ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); // Transfer funds to sellers transfer( borrower, seller, asset(100000,mpa_id) ); @@ -478,7 +478,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_small_limit_taker_test ) BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1156259,mpa_id), asset(18680) ) ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); @@ -545,7 +545,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_small_limit_taker_test ) BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == price( asset(896649,mpa_id), asset(19620) ) ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK_EQUAL( call_id(db).debt.value, 68973 ); BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1962 ); @@ -646,7 +646,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) // check BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1250,mpa_id), asset(2000) ) ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed // 1000 * (2000/1250) * 1.9 = 3040 @@ -665,7 +665,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) // check BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1250,mpa_id), asset(2100) ) ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); // Sam update MSSR and MCFR // note: borrower's position is undercollateralized again due to the mssr change @@ -685,7 +685,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) BOOST_CHECK_EQUAL( mpa.bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1300u ); BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1300,mpa_id), asset(2100) ) ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); // Transfer funds to sellers transfer( borrower, seller, asset(1000,mpa_id) ); @@ -738,7 +738,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(11557,mpa_id), asset(18670) ) ); // 13:10 * (1000-111):(2100-111*210/100) // 13:10 * 889:1867 - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); @@ -796,7 +796,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(8957,mpa_id), asset(19510) ) ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); BOOST_CHECK_EQUAL( call_id(db).debt.value, 689 ); BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1951 ); @@ -826,7 +826,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( !db.find(call_id) ); BOOST_CHECK( !db.find(call2_id) ); @@ -924,7 +924,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_small_settle_taker_test ) // check BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2000) ) ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed // 100000 * (2000/125000) * 1.9 = 3040 @@ -943,7 +943,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_small_settle_taker_test ) // check BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2100) ) ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); // Sam update MSSR and MCFR // note: borrower's position is undercollateralized again due to the mssr change @@ -963,7 +963,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_small_settle_taker_test ) BOOST_CHECK_EQUAL( mpa.bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1300u ); BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(130000,mpa_id), asset(2100) ) ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); // Transfer funds to sellers transfer( borrower, seller, asset(100000,mpa_id) ); @@ -1025,7 +1025,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_small_settle_taker_test ) BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == price( asset(1156259,mpa_id), asset(18680) ) ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); @@ -1327,7 +1327,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_taker_test ) auto check_result = [&] { BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), expected_seller_balance_mpa ); BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), expected_seller2_balance_mpa ); BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); @@ -2254,7 +2254,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_update_debt_test ) // check BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2000) ) ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed // 100000 * (2000/125000) * 1.9 = 3040 @@ -2273,7 +2273,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_update_debt_test ) // check BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2100) ) ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); // Sam update MSSR and MCFR // note: borrower's position is undercollateralized again due to the mssr change @@ -2293,7 +2293,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_update_debt_test ) BOOST_CHECK_EQUAL( mpa.bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1300u ); BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(130000,mpa_id), asset(2100) ) ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).is_globally_settled() ); // Transfer funds to seller2 transfer( borrower3, seller2, asset(50000,mpa_id) ); @@ -2376,7 +2376,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_update_debt_test ) { BOOST_TEST_MESSAGE( "Check result"); BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); diff --git a/tests/tests/credit_offer_tests.cpp b/tests/tests/credit_offer_tests.cpp index 1c3e0a11aa..8c485961ed 100644 --- a/tests/tests/credit_offer_tests.cpp +++ b/tests/tests/credit_offer_tests.cpp @@ -38,8 +38,7 @@ using namespace graphene::chain::test; BOOST_FIXTURE_TEST_SUITE( credit_offer_tests, database_fixture ) BOOST_AUTO_TEST_CASE( credit_offer_hardfork_time_test ) -{ - try { +{ try { // Proceeds to a recent hard fork generate_blocks( HARDFORK_CORE_2262_TIME ); @@ -107,17 +106,95 @@ BOOST_AUTO_TEST_CASE( credit_offer_hardfork_time_test ) set_expiration( db, trx ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( credit_deal_auto_repay_hardfork_time_test ) +{ try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_CORE_2362_TIME ); + set_expiration( db, trx ); + + ACTORS((ray)(sam)(ted)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( ray, asset(init_amount) ); + fund( sam, asset(init_amount) ); + + const asset_object& core = asset_id_type()(db); + asset_id_type core_id; + + const asset_object& usd = create_user_issued_asset( "MYUSD", ted, white_list ); + asset_id_type usd_id = usd.get_id(); + issue_uia( ray, usd.amount(init_amount) ); + issue_uia( sam, usd.amount(init_amount) ); + + const asset_object& eur = create_user_issued_asset( "MYEUR", sam, white_list ); + asset_id_type eur_id = eur.get_id(); + issue_uia( ray, eur.amount(init_amount) ); + issue_uia( sam, eur.amount(init_amount) ); + + // create a credit offer + auto disable_time1 = db.head_block_time() + fc::minutes(20); // 20 minutes after init + + flat_map collateral_map1; + collateral_map1[usd_id] = price( asset(1), asset(2, usd_id) ); + collateral_map1[eur_id] = price( asset(1), asset(1, eur_id) ); + + const credit_offer_object& coo1 = create_credit_offer( sam_id, core.get_id(), 10000, 30000, 3600, 0, true, + disable_time1, collateral_map1, {} ); + credit_offer_id_type co1_id = coo1.get_id(); + + // Before the hard fork, unable to borrow with the "auto-repay" extension field enabled + // or propose to do so + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100), + asset(200, usd_id), GRAPHENE_FEE_RATE_DENOM, 0, 0 ), + fc::exception ); + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100), + asset(200, usd_id), GRAPHENE_FEE_RATE_DENOM, 0, 1 ), + fc::exception ); + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100), + asset(200, usd_id), GRAPHENE_FEE_RATE_DENOM, 0, 2 ), + fc::exception ); + + credit_offer_accept_operation accop = make_credit_offer_accept_op( ray_id, co1_id, asset(100), + asset(200, usd_id), GRAPHENE_FEE_RATE_DENOM, 0, 0 ); + BOOST_CHECK_THROW( propose( accop ), fc::exception ); + + accop = make_credit_offer_accept_op( ray_id, co1_id, asset(100), + asset(200, usd_id), GRAPHENE_FEE_RATE_DENOM, 0, 1 ); + BOOST_CHECK_THROW( propose( accop ), fc::exception ); + + accop = make_credit_offer_accept_op( ray_id, co1_id, asset(100), + asset(200, usd_id), GRAPHENE_FEE_RATE_DENOM, 0, 2 ); + BOOST_CHECK_THROW( propose( accop ), fc::exception ); + + // borrow + BOOST_TEST_MESSAGE( "Ray borrows" ); + const credit_deal_object& cdo11 = borrow_from_credit_offer( ray_id, co1_id, asset(100), + asset(200, usd_id), GRAPHENE_FEE_RATE_DENOM, 0, {} ); + credit_deal_id_type cd11_id = cdo11.get_id(); + + BOOST_CHECK_EQUAL( cd11_id(db).auto_repay, 0U ); + + // Before the hard fork, unable to update a credit deal + // or update with proposals + BOOST_CHECK_THROW( update_credit_deal( ray_id, cd11_id, 1 ), fc::exception ); + + credit_deal_update_operation updop = make_credit_deal_update_op( ray_id, cd11_id, 1 ); + BOOST_CHECK_THROW( propose( updop ), fc::exception ); + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( credit_offer_crud_and_proposal_test ) { try { // Pass the hard fork time - generate_blocks( HARDFORK_CORE_2362_TIME ); + if( hf2595 ) + generate_blocks( HARDFORK_CORE_2595_TIME ); + else + generate_blocks( HARDFORK_CORE_2362_TIME ); + set_expiration( db, trx ); ACTORS((sam)(ted)(por)); @@ -669,17 +746,16 @@ BOOST_AUTO_TEST_CASE( credit_offer_crud_and_proposal_test ) generate_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( credit_offer_borrow_repay_test ) { try { // Pass the hard fork time - generate_blocks( HARDFORK_CORE_2362_TIME ); + if( hf2595 ) + generate_blocks( HARDFORK_CORE_2595_TIME ); + else + generate_blocks( HARDFORK_CORE_2362_TIME ); set_expiration( db, trx ); ACTORS((ray)(sam)(ted)(por)); @@ -801,7 +877,7 @@ BOOST_AUTO_TEST_CASE( credit_offer_borrow_repay_test ) check_balances(); - // Unable to borrow : the credit offer is disabled + // Unable to borrow : the credit offer does not exist credit_offer_id_type tmp_co_id; BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, tmp_co_id, asset(100), asset(200, usd_id) ), fc::exception ); @@ -1766,12 +1842,374 @@ BOOST_AUTO_TEST_CASE( credit_offer_borrow_repay_test ) generate_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( credit_offer_crud_and_proposal_test_after_hf2595 ) +{ + hf2595 = true; + INVOKE( credit_offer_crud_and_proposal_test ); } +BOOST_AUTO_TEST_CASE( credit_offer_borrow_repay_test_after_hf2595 ) +{ + hf2595 = true; + INVOKE( credit_offer_borrow_repay_test ); +} + +BOOST_AUTO_TEST_CASE( credit_deal_auto_repay_test ) +{ try { + + // Pass the hard fork time + generate_blocks( HARDFORK_CORE_2595_TIME ); + set_expiration( db, trx ); + + ACTORS((ray)(sam)(ted)(por)(np1)(np2)(np3)(np4)(np5)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( por, asset(init_amount) ); + fund( np4, asset(init_amount) ); + fund( np5, asset(init_amount) ); + + asset_id_type core_id; + + const asset_object& usd = create_user_issued_asset( "MYUSD", ted, white_list ); + asset_id_type usd_id = usd.get_id(); + issue_uia( sam, usd.amount(init_amount) ); + + const asset_object& eur = create_user_issued_asset( "MYEUR", sam, white_list ); + asset_id_type eur_id = eur.get_id(); + issue_uia( ray, eur.amount(init_amount) ); + issue_uia( np1, eur.amount(init_amount) ); + issue_uia( np2, eur.amount(init_amount) ); + issue_uia( np3, eur.amount(init_amount) ); + + const asset_object& jpy = create_user_issued_asset( "MYJPY", ted, white_list ); + asset_id_type jpy_id = jpy.get_id(); + issue_uia( sam, jpy.amount(init_amount * 1000) ); + + BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, 0 ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, init_amount ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, eur_id ).amount.value, 0 ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, jpy_id ).amount.value, init_amount * 1000 ); + + // create a credit offer + auto disable_time1 = db.head_block_time() + fc::minutes(20); // 20 minutes after init + + flat_map collateral_map1; + collateral_map1[core_id] = price( asset(19, usd_id), asset(23, core_id) ); + collateral_map1[eur_id] = price( asset(19, usd_id), asset(17, eur_id) ); + + const credit_offer_object& coo1 = create_credit_offer( sam_id, usd_id, 10000, 30000, 3600, 0, true, + disable_time1, collateral_map1, {} ); + credit_offer_id_type co1_id = coo1.get_id(); + + // create another credit offer with a very high fee rate + flat_map collateral_map2; + collateral_map2[core_id] = price( asset(init_amount, jpy_id), asset(1, core_id) ); + + const credit_offer_object& coo2 = create_credit_offer( sam_id, jpy_id, init_amount * 1000, + 4000 * GRAPHENE_FEE_RATE_DENOM, 3600, 0, true, + disable_time1, collateral_map2, {} ); + credit_offer_id_type co2_id = coo2.get_id(); + + BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, 0 ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, init_amount - 10000 ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, eur_id ).amount.value, 0 ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, jpy_id ).amount.value, 0 ); + + // Unable to accept offer with an invalid auto-repayment type + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(190, usd_id), + asset(170, eur_id), GRAPHENE_FEE_RATE_DENOM, 0, 3 ), + fc::exception ); + // Unable to accept offer with a proposal with an invalid auto-repayment type + credit_offer_accept_operation accop = make_credit_offer_accept_op( ray_id, co1_id, asset(190, usd_id), + asset(170, eur_id), GRAPHENE_FEE_RATE_DENOM, 0, 3 ); + BOOST_CHECK_THROW( propose( accop ), fc::exception ); + + // Able to accept offer with a valid auto-repayment type + const credit_deal_object& cdo11 = borrow_from_credit_offer( ray_id, co1_id, asset(190, usd_id), + asset(170, eur_id), GRAPHENE_FEE_RATE_DENOM, 0, 2 ); + credit_deal_id_type cd11_id = cdo11.get_id(); + + BOOST_CHECK_EQUAL( cd11_id(db).auto_repay, 2U ); + + // Unable to update the deal with an invalid auto-repayment type + BOOST_CHECK_THROW( update_credit_deal( ray_id, cd11_id, 3 ), fc::exception ); + // Unable to propose an update with an invalid auto-repayment type + credit_deal_update_operation updop = make_credit_deal_update_op( ray_id, cd11_id, 3 ); + BOOST_CHECK_THROW( propose( updop ), fc::exception ); + + // Unable to update the deal if nothing would change + BOOST_CHECK_THROW( update_credit_deal( ray_id, cd11_id, 2 ), fc::exception ); + // Unable to update a deal if it does not exist + BOOST_CHECK_THROW( update_credit_deal( ray_id, cd11_id + 100, 1 ), fc::exception ); + // Unable to update a deal if the account is not the owner + BOOST_CHECK_THROW( update_credit_deal( sam_id, cd11_id, 1 ), fc::exception ); + + // Able to propose an update with a valid auto-repayment type + updop = make_credit_deal_update_op( sam_id, cd11_id + 100, 2 ); + propose( updop ); + + // Able to update a deal with valid data + update_credit_deal( ray_id, cd11_id, 1 ); + BOOST_CHECK_EQUAL( cd11_id(db).auto_repay, 1U ); + update_credit_deal( ray_id, cd11_id, 0 ); + BOOST_CHECK_EQUAL( cd11_id(db).auto_repay, 0U ); + update_credit_deal( ray_id, cd11_id, 2 ); + BOOST_CHECK_EQUAL( cd11_id(db).auto_repay, 2U ); + + // People borrow more + const credit_deal_object& cdo12 = borrow_from_credit_offer( por_id, co1_id, asset(190, usd_id), + asset(310), GRAPHENE_FEE_RATE_DENOM, 0, 1 ); + credit_deal_id_type cd12_id = cdo12.get_id(); + + BOOST_CHECK_EQUAL( cd12_id(db).auto_repay, 1U ); + + const credit_deal_object& cdo13 = borrow_from_credit_offer( np1_id, co1_id, asset(190, usd_id), + asset(170, eur_id), GRAPHENE_FEE_RATE_DENOM, 0, 0 ); + credit_deal_id_type cd13_id = cdo13.get_id(); + + BOOST_CHECK_EQUAL( cd13_id(db).auto_repay, 0U ); + + const credit_deal_object& cdo14 = borrow_from_credit_offer( np2_id, co1_id, asset(190, usd_id), + asset(170, eur_id), GRAPHENE_FEE_RATE_DENOM, 0, 2 ); + credit_deal_id_type cd14_id = cdo14.get_id(); + + BOOST_CHECK_EQUAL( cd14_id(db).auto_repay, 2U ); + + const credit_deal_object& cdo15 = borrow_from_credit_offer( np3_id, co1_id, asset(190, usd_id), + asset(170, eur_id), GRAPHENE_FEE_RATE_DENOM, 0, 2 ); + credit_deal_id_type cd15_id = cdo15.get_id(); + + BOOST_CHECK_EQUAL( cd15_id(db).auto_repay, 2U ); + + const credit_deal_object& cdo16 = borrow_from_credit_offer( ray_id, co1_id, asset(190, usd_id), + asset(170, eur_id), GRAPHENE_FEE_RATE_DENOM, 0, 1 ); + credit_deal_id_type cd16_id = cdo16.get_id(); + + BOOST_CHECK_EQUAL( cd16_id(db).auto_repay, 1U ); + + const credit_deal_object& cdo17 = borrow_from_credit_offer( ray_id, co1_id, asset(190, usd_id), + asset(170, eur_id), GRAPHENE_FEE_RATE_DENOM, 0, 2 ); + credit_deal_id_type cd17_id = cdo17.get_id(); + + BOOST_CHECK_EQUAL( cd17_id(db).auto_repay, 2U ); + + BOOST_CHECK_EQUAL( db.get_balance( ray_id, usd_id ).amount.value, 190 * 3 ); + BOOST_CHECK_EQUAL( db.get_balance( ray_id, eur_id ).amount.value, init_amount - 170 * 3 ); + BOOST_CHECK_EQUAL( db.get_balance( por_id, usd_id ).amount.value, 190 ); + BOOST_CHECK_EQUAL( db.get_balance( por_id, core_id ).amount.value, init_amount - 310 ); + BOOST_CHECK_EQUAL( db.get_balance( np1_id, usd_id ).amount.value, 190 ); + BOOST_CHECK_EQUAL( db.get_balance( np1_id, eur_id ).amount.value, init_amount - 170 ); + BOOST_CHECK_EQUAL( db.get_balance( np2_id, usd_id ).amount.value, 190 ); + BOOST_CHECK_EQUAL( db.get_balance( np2_id, eur_id ).amount.value, init_amount - 170 ); + BOOST_CHECK_EQUAL( db.get_balance( np3_id, usd_id ).amount.value, 190 ); + BOOST_CHECK_EQUAL( db.get_balance( np3_id, eur_id ).amount.value, init_amount - 170 ); + + BOOST_CHECK_EQUAL( co1_id(db).total_balance.value, 10000 ); + BOOST_CHECK_EQUAL( co1_id(db).current_balance.value, 8670 ); // 10000 - 190 * 7 + + const credit_deal_object& cdo21 = borrow_from_credit_offer( np4_id, co2_id, + asset(init_amount * 999, jpy_id), + asset(999), GRAPHENE_FEE_RATE_DENOM * 4000, 0, 1 ); + credit_deal_id_type cd21_id = cdo21.get_id(); + + BOOST_CHECK_EQUAL( cd21_id(db).auto_repay, 1U ); + + const credit_deal_object& cdo22 = borrow_from_credit_offer( np5_id, co2_id, + asset(init_amount, jpy_id), + asset(1), GRAPHENE_FEE_RATE_DENOM * 4000, 0, 2 ); + credit_deal_id_type cd22_id = cdo22.get_id(); + + BOOST_CHECK_EQUAL( cd22_id(db).auto_repay, 2U ); + + BOOST_CHECK_EQUAL( db.get_balance( np4_id, jpy_id ).amount.value, init_amount * 999 ); + BOOST_CHECK_EQUAL( db.get_balance( np4_id, core_id ).amount.value, init_amount - 999 ); + BOOST_CHECK_EQUAL( db.get_balance( np5_id, jpy_id ).amount.value, init_amount ); + BOOST_CHECK_EQUAL( db.get_balance( np5_id, core_id ).amount.value, init_amount - 1 ); + + BOOST_CHECK_EQUAL( co2_id(db).total_balance.value, init_amount * 1000 ); + BOOST_CHECK_EQUAL( co2_id(db).current_balance.value, 0 ); + + // Setup blacklists + { + BOOST_TEST_MESSAGE( "Setting up EUR blacklisting" ); + asset_update_operation uop; + uop.asset_to_update = eur.id; + uop.issuer = sam_id; + uop.new_options = eur.options; + // The EUR blacklist is managed by Sam + uop.new_options.blacklist_authorities.insert(sam_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Upgrade Sam so that he can manage the blacklist + upgrade_to_lifetime_member( sam_id ); + + // Add np2 to the EUR blacklist + account_whitelist_operation wop; + wop.authorizing_account = sam_id; + wop.account_to_list = np2_id; + wop.new_listing = account_whitelist_operation::black_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + { + BOOST_TEST_MESSAGE( "Setting up USD blacklisting" ); + asset_update_operation uop; + uop.asset_to_update = usd.id; + uop.issuer = ted_id; + uop.new_options = usd.options; + // The USD blacklist is managed by Ted + uop.new_options.blacklist_authorities.insert(ted_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Upgrade Ted so that he can manage the blacklist + upgrade_to_lifetime_member( ted_id ); + + // Add np3 to the USD blacklist + account_whitelist_operation wop; + wop.authorizing_account = ted_id; + wop.account_to_list = np3_id; + wop.new_listing = account_whitelist_operation::black_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + // Let the credit deals expire + generate_blocks( db.head_block_time() + 3600 ); + generate_block(); + + BOOST_CHECK( !db.find( cd11_id ) ); + BOOST_CHECK( !db.find( cd12_id ) ); + BOOST_CHECK( !db.find( cd13_id ) ); + BOOST_CHECK( !db.find( cd14_id ) ); + BOOST_CHECK( !db.find( cd15_id ) ); + BOOST_CHECK( !db.find( cd16_id ) ); + BOOST_CHECK( !db.find( cd17_id ) ); + BOOST_CHECK( !db.find( cd21_id ) ); + BOOST_CHECK( !db.find( cd22_id ) ); + + // Ray fully repaid cd11, fee = round_up(190 * 3 / 100) = 6 + // Por is unable to repay cd12 due to insufficient balance + // Np1 decided to not pay cd13 + // Np2 failed to repay cd14 due to blacklisting + // Np3 is blacklisted by the collateral asset (EUR), however it doesn't affect the repayment for cd15 + // Balance was 190 + // cd15 debt was 190, collateral was 170 + // To repay: round_down(190 * 100 / 103) = 184 + // Collateral released = round_down(184 * 170 / 190) = 164 + // Updated repay amount = round_up(164 * 190 / 170) = 184 + // Fee = round_up(184 * 3 / 100) = 6 + // Total Pays = 184 + 6 = 190 + // New balance = 190 - 190 = 0 + // cd17 unpaid debt = 190 - 184 = 6, unreleased collateral = 170 - 164 = 6 + // Ray fully repaid cd16, fee = round_up(190 * 3 / 100) = 6 + // Ray partially repaid cd17 + // Balance was 190 - 6 * 2 = 178 + // cd17 debt was 190, collateral was 170 + // To repay: round_down(178 * 100 / 103) = 172 + // Collateral released = round_down(172 * 170 / 190) = 153 + // Updated repay amount = round_up(153 * 190 / 170) = 171 + // Fee = round_up(171 * 3 / 100) = 6 + // Total Pays = 171 + 6 = 177 + // New balance = 178 - 177 = 1 + // cd17 unpaid debt = 190 - 171 = 19, unreleased collateral = 170 - 153 = 17 + // Np4 is unable to repay cd21 due to amount overflow or insufficient balance + // Np5 did not repay cd22 because no collateral will be returned on partial repayment + + BOOST_CHECK_EQUAL( co1_id(db).total_balance.value, 9429 ); // 10000 - 190 * 3 - 6 - 19 + 6 * 4 + BOOST_CHECK_EQUAL( co1_id(db).current_balance.value, 9429 ); + + BOOST_CHECK_EQUAL( co2_id(db).total_balance.value, 0 ); + BOOST_CHECK_EQUAL( co2_id(db).current_balance.value, 0 ); + + BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, 310 + 999 + 1 ); // cd13, cd21, cd22 + BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, init_amount - 10000 ); // no change + BOOST_CHECK_EQUAL( db.get_balance( sam_id, eur_id ).amount.value, 170 * 2 + 6 + 17 ); // cd12, cd14, cd15, cd17 + BOOST_CHECK_EQUAL( db.get_balance( sam_id, jpy_id ).amount.value, 0 ); // no change + + BOOST_CHECK_EQUAL( db.get_balance( ray_id, usd_id ).amount.value, 1 ); + BOOST_CHECK_EQUAL( db.get_balance( ray_id, eur_id ).amount.value, init_amount - 17 ); + BOOST_CHECK_EQUAL( db.get_balance( por_id, usd_id ).amount.value, 190 ); + BOOST_CHECK_EQUAL( db.get_balance( por_id, core_id ).amount.value, init_amount - 310 ); + BOOST_CHECK_EQUAL( db.get_balance( np1_id, usd_id ).amount.value, 190 ); + BOOST_CHECK_EQUAL( db.get_balance( np1_id, eur_id ).amount.value, init_amount - 170 ); + BOOST_CHECK_EQUAL( db.get_balance( np2_id, usd_id ).amount.value, 0 ); + BOOST_CHECK_EQUAL( db.get_balance( np2_id, eur_id ).amount.value, init_amount - 6 ); + BOOST_CHECK_EQUAL( db.get_balance( np3_id, usd_id ).amount.value, 190 ); + BOOST_CHECK_EQUAL( db.get_balance( np3_id, eur_id ).amount.value, init_amount - 170 ); + + BOOST_CHECK_EQUAL( db.get_balance( np4_id, jpy_id ).amount.value, init_amount * 999 ); + BOOST_CHECK_EQUAL( db.get_balance( np4_id, core_id ).amount.value, init_amount - 999 ); + BOOST_CHECK_EQUAL( db.get_balance( np5_id, jpy_id ).amount.value, init_amount ); + BOOST_CHECK_EQUAL( db.get_balance( np5_id, core_id ).amount.value, init_amount - 1 ); + + // Check history API + graphene::app::history_api hist_api(app); + + // np5's last 2 operations are credit_deal_expired_op and credit_offer_accept_op + auto histories = hist_api.get_relative_account_history( "np5", 0, 2, 0 ); + BOOST_REQUIRE_EQUAL( histories.size(), 2U ); + BOOST_CHECK( histories[0].op.is_type() ); + BOOST_CHECK( histories[0].is_virtual ); + BOOST_CHECK( histories[1].op.is_type() ); + BOOST_CHECK( !histories[1].is_virtual ); + + // np4's last 2 operations are credit_deal_expired_op and credit_offer_accept_op + histories = hist_api.get_relative_account_history( "np4", 0, 2, 0 ); + BOOST_REQUIRE_EQUAL( histories.size(), 2U ); + BOOST_CHECK( histories[0].op.is_type() ); + BOOST_CHECK( histories[0].is_virtual ); + BOOST_CHECK( histories[1].op.is_type() ); + BOOST_CHECK( !histories[1].is_virtual ); + + // np2's last 4 operations are credit_deal_expired_op, credit_deal_repay_op, account_whitelist_op, + // and credit_offer_accept_op + histories = hist_api.get_relative_account_history( "np2", 0, 4, 0 ); + BOOST_REQUIRE_EQUAL( histories.size(), 4U ); + BOOST_CHECK( histories[0].op.is_type() ); + BOOST_CHECK( histories[0].is_virtual ); + BOOST_CHECK( histories[1].op.is_type() ); + BOOST_CHECK( histories[1].is_virtual ); + BOOST_CHECK( histories[2].op.is_type() ); + BOOST_CHECK( !histories[2].is_virtual ); + BOOST_CHECK( histories[3].op.is_type() ); + BOOST_CHECK( !histories[3].is_virtual ); + + // ray's last 10 operations are 1 * credit_deal_expired_op, 3 * credit_deal_repay_op, + // 2 * credit_offer_accept_op, + // 3 * credit_deal_update_op, 1 * credit_accept_op + histories = hist_api.get_relative_account_history( "ray", 0, 10, 0 ); + BOOST_REQUIRE_EQUAL( histories.size(), 10U ); + BOOST_CHECK( histories[0].op.is_type() ); + BOOST_CHECK( histories[0].is_virtual ); + BOOST_CHECK( histories[1].op.is_type() ); + BOOST_CHECK( histories[1].is_virtual ); + BOOST_CHECK( histories[2].op.is_type() ); + BOOST_CHECK( histories[2].is_virtual ); + BOOST_CHECK( histories[3].op.is_type() ); + BOOST_CHECK( histories[3].is_virtual ); + BOOST_CHECK( histories[4].op.is_type() ); + BOOST_CHECK( !histories[4].is_virtual ); + BOOST_CHECK( histories[5].op.is_type() ); + BOOST_CHECK( !histories[5].is_virtual ); + BOOST_CHECK( histories[6].op.is_type() ); + BOOST_CHECK( !histories[6].is_virtual ); + BOOST_CHECK( histories[7].op.is_type() ); + BOOST_CHECK( !histories[7].is_virtual ); + BOOST_CHECK( histories[8].op.is_type() ); + BOOST_CHECK( !histories[8].is_virtual ); + BOOST_CHECK( histories[9].op.is_type() ); + BOOST_CHECK( !histories[9].is_virtual ); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE( credit_offer_apis_test ) { try { @@ -1992,10 +2430,6 @@ BOOST_AUTO_TEST_CASE( credit_offer_apis_test ) BOOST_CHECK( deals[2].id == cd21_id ); BOOST_CHECK( deals[3].id == cd51_id ); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/fee_tests.cpp b/tests/tests/fee_tests.cpp index f8d13323c7..49a3cd69e8 100644 --- a/tests/tests/fee_tests.cpp +++ b/tests/tests/fee_tests.cpp @@ -733,6 +733,7 @@ BOOST_AUTO_TEST_CASE( fee_refund_test ) issue_uia( bob_id, asset( bob_b0, usd_id ) ); int64_t order_create_fee = 537; + int64_t order_update_fee = 437; int64_t order_cancel_fee = 129; uint32_t skip = database::skip_witness_signature @@ -745,21 +746,27 @@ BOOST_AUTO_TEST_CASE( fee_refund_test ) generate_block( skip ); - for( int i=0; i<2; i++ ) + for( int i=0; i<5; i++ ) { if( i == 1 ) { generate_blocks( HARDFORK_445_TIME, true, skip ); generate_block( skip ); } + else if( i == 2 ) + { + generate_blocks( HARDFORK_CORE_1604_TIME, true, skip ); + generate_block( skip ); + } + // enable_fees() and change_fees() modifies DB directly, and results will be overwritten by block generation // so we have to do it every time we stop generating/popping blocks and start doing tx's enable_fees(); /* change_fees({ - limit_order_create_operation::fee_parameters_type { order_create_fee }, - limit_order_cancel_operation::fee_parameters_type { order_cancel_fee } + limit_order_create_operation::fee_params_t { order_create_fee }, + limit_order_cancel_operation::fee_params_t { order_cancel_fee } }); */ // C++ -- The above commented out statement doesn't work, I don't know why @@ -767,12 +774,17 @@ BOOST_AUTO_TEST_CASE( fee_refund_test ) { fee_parameters::flat_set_type new_fees; { - limit_order_create_operation::fee_parameters_type create_fee_params; + limit_order_create_operation::fee_params_t create_fee_params; create_fee_params.fee = order_create_fee; new_fees.insert( create_fee_params ); } { - limit_order_cancel_operation::fee_parameters_type cancel_fee_params; + limit_order_update_operation::fee_params_t update_fee_params; + update_fee_params.fee = order_update_fee; + new_fees.insert( update_fee_params ); + } + { + limit_order_cancel_operation::fee_params_t cancel_fee_params; cancel_fee_params.fee = order_cancel_fee; new_fees.insert( cancel_fee_params ); } @@ -795,6 +807,21 @@ BOOST_AUTO_TEST_CASE( fee_refund_test ) BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - order_create_fee ); BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 - 500 ); + int64_t update_net_fee = order_cancel_fee * order_update_fee / order_create_fee ; + int64_t bob_update_fees = 0; + if( i == 2 ) + { + // Bob updates order + update_limit_order( bo1_id, {}, asset(100, usd_id) ); + + bob_update_fees += update_net_fee; + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - 1000 - order_create_fee ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - bob_update_fees - order_update_fee ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 - 600 ); + } + // Bob cancels order cancel_limit_order( bo1_id(db) ); @@ -806,7 +833,7 @@ BOOST_AUTO_TEST_CASE( fee_refund_test ) BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - 1000 - order_create_fee ); BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 ); - BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - cancel_net_fee ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - bob_update_fees - cancel_net_fee ); BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 ); // Alice cancels order @@ -814,27 +841,62 @@ BOOST_AUTO_TEST_CASE( fee_refund_test ) BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee ); BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 ); - BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - cancel_net_fee ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - bob_update_fees - cancel_net_fee ); BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 ); // Check partial fill const limit_order_object* ao2 = create_sell_order( alice_id, asset(1000), asset(200, usd_id) ); + + BOOST_REQUIRE( ao2 != nullptr ); + + int64_t alice_update_fees = order_create_fee; + int64_t alice_update_amounts = 0; + if( i == 3 ) + { + // Alice updates order + update_limit_order( *ao2, {}, asset(100) ); + + alice_update_fees = update_net_fee + order_update_fee; + alice_update_amounts = 100; + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee - alice_update_fees + - 1000 - alice_update_amounts ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 ); + } + const limit_order_object* bo2 = create_sell_order( bob_id, asset(100, usd_id), asset(500) ); - BOOST_CHECK( ao2 != nullptr ); BOOST_CHECK( bo2 == nullptr ); - BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee - order_create_fee - 1000 ); + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee - alice_update_fees + - 1000 - alice_update_amounts ); BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 + 100 ); - BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - cancel_net_fee - order_create_fee + 500 ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - bob_update_fees - cancel_net_fee + - order_create_fee + 500 ); BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 - 100 ); + if( i == 4 ) + { + // Alice updates order + update_limit_order( *ao2, {}, asset(100) ); + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee - alice_update_fees + - 1000 - alice_update_amounts + - order_update_fee - 100 ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 + 100 ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - bob_update_fees - cancel_net_fee + - order_create_fee + 500 ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 - 100 ); + } + // cancel Alice order, show that entire deferred_fee was consumed by partial match cancel_limit_order( *ao2 ); - BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee - order_create_fee - 500 - order_cancel_fee ); + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee - alice_update_fees - 500 + - order_cancel_fee ); BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 + 100 ); - BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - cancel_net_fee - order_create_fee + 500 ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - bob_update_fees - cancel_net_fee + - order_create_fee + 500 ); BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 - 100 ); // TODO: Check multiple fill @@ -884,17 +946,17 @@ BOOST_AUTO_TEST_CASE( non_core_fee_refund_test ) fee_parameters::flat_set_type new_fees; { - limit_order_create_operation::fee_parameters_type create_fee_params; + limit_order_create_operation::fee_params_t create_fee_params; create_fee_params.fee = order_create_fee; new_fees.insert( create_fee_params ); } { - limit_order_cancel_operation::fee_parameters_type cancel_fee_params; + limit_order_cancel_operation::fee_params_t cancel_fee_params; cancel_fee_params.fee = order_cancel_fee; new_fees.insert( cancel_fee_params ); } { - transfer_operation::fee_parameters_type transfer_fee_params; + transfer_operation::fee_params_t transfer_fee_params; transfer_fee_params.fee = 0; transfer_fee_params.price_per_kbyte = 0; new_fees.insert( transfer_fee_params ); @@ -921,7 +983,8 @@ BOOST_AUTO_TEST_CASE( non_core_fee_refund_test ) // prepare params uint32_t blocks_generated = 0; time_point_sec max_exp = time_point_sec::maximum(); - time_point_sec exp = db.head_block_time(); // order will be accepted when pushing trx then expired at current block + time_point_sec exp = db.head_block_time(); // order will be accepted when pushing trx then + // expired at current block price cer( asset(1), asset(1, usd_id) ); const auto* usd_stat = &usd_id( db ).dynamic_asset_data_id( db ); @@ -1028,10 +1091,10 @@ BOOST_AUTO_TEST_CASE( non_core_fee_refund_test ) // Check partial fill const limit_order_object* ao2 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), exp, cer ); - const limit_order_id_type ao2id = ao2->get_id(); + const limit_order_id_type ao2_id = ao2->get_id(); const limit_order_object* bo2 = create_sell_order( bob_id, asset(100, usd_id), asset(500) ); - BOOST_CHECK( db.find( ao2id ) != nullptr ); + BOOST_CHECK( db.find( ao2_id ) != nullptr ); BOOST_CHECK( bo2 == nullptr ); // data after order created @@ -1271,17 +1334,17 @@ BOOST_AUTO_TEST_CASE( hf445_fee_refund_cross_test ) fee_parameters::flat_set_type new_fees; { - limit_order_create_operation::fee_parameters_type create_fee_params; + limit_order_create_operation::fee_params_t create_fee_params; create_fee_params.fee = order_create_fee; new_fees.insert( create_fee_params ); } { - limit_order_cancel_operation::fee_parameters_type cancel_fee_params; + limit_order_cancel_operation::fee_params_t cancel_fee_params; cancel_fee_params.fee = order_cancel_fee; new_fees.insert( cancel_fee_params ); } { - transfer_operation::fee_parameters_type transfer_fee_params; + transfer_operation::fee_params_t transfer_fee_params; transfer_fee_params.fee = 0; transfer_fee_params.price_per_kbyte = 0; new_fees.insert( transfer_fee_params ); @@ -1764,6 +1827,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) fund_fee_pool( committee_account( db ), usd_obj, pool_0 ); int64_t order_create_fee = 547; + int64_t order_update_fee = 487; int64_t order_cancel_fee; int64_t order_cancel_fee1 = 139; int64_t order_cancel_fee2 = 829; @@ -1782,36 +1846,43 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) fee_parameters::flat_set_type new_fees1; fee_parameters::flat_set_type new_fees2; { - limit_order_create_operation::fee_parameters_type create_fee_params; + limit_order_create_operation::fee_params_t create_fee_params; create_fee_params.fee = order_create_fee; new_fees1.insert( create_fee_params ); new_fees2.insert( create_fee_params ); } { - limit_order_cancel_operation::fee_parameters_type cancel_fee_params; + limit_order_update_operation::fee_params_t update_fee_params; + update_fee_params.fee = order_update_fee; + new_fees1.insert( update_fee_params ); + new_fees2.insert( update_fee_params ); + } + { + limit_order_cancel_operation::fee_params_t cancel_fee_params; cancel_fee_params.fee = order_cancel_fee1; new_fees1.insert( cancel_fee_params ); } { - limit_order_cancel_operation::fee_parameters_type cancel_fee_params; + limit_order_cancel_operation::fee_params_t cancel_fee_params; cancel_fee_params.fee = order_cancel_fee2; new_fees2.insert( cancel_fee_params ); } { - transfer_operation::fee_parameters_type transfer_fee_params; + transfer_operation::fee_params_t transfer_fee_params; transfer_fee_params.fee = 0; transfer_fee_params.price_per_kbyte = 0; new_fees1.insert( transfer_fee_params ); new_fees2.insert( transfer_fee_params ); } - for( int i=0; i<12; i++ ) + for( int i=0; i<16; i++ ) { bool expire_order = ( i % 2 != 0 ); bool high_cancel_fee = ( i % 4 >= 2 ); bool before_hardfork_445 = ( i < 4 ); bool after_bsip26 = ( i >= 8 ); - idump( (before_hardfork_445)(after_bsip26)(expire_order)(high_cancel_fee) ); + bool after_core_hf1604 = ( i >= 12 ); + idump( (before_hardfork_445)(after_bsip26)(after_core_hf1604)(expire_order)(high_cancel_fee) ); if( i == 4 ) { BOOST_TEST_MESSAGE( "Hard fork 445" ); @@ -1824,6 +1895,12 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) generate_blocks( HARDFORK_CORE_604_TIME, true, skip ); generate_block( skip ); } + else if( i == 12 ) + { + BOOST_TEST_MESSAGE( "Hard fork core-1604" ); + generate_blocks( HARDFORK_CORE_1604_TIME, true, skip ); + generate_block( skip ); + } if( high_cancel_fee ) { @@ -1838,9 +1915,12 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) int64_t usd_create_fee = order_create_fee * cer_usd_amount / cer_core_amount; if( usd_create_fee * cer_core_amount != order_create_fee * cer_usd_amount ) usd_create_fee += 1; + int64_t usd_update_fee = order_update_fee * cer_usd_amount / cer_core_amount; + if( usd_update_fee * cer_core_amount != order_update_fee * cer_usd_amount ) usd_update_fee += 1; int64_t usd_cancel_fee = order_cancel_fee * cer_usd_amount / cer_core_amount; if( usd_cancel_fee * cer_core_amount != order_cancel_fee * cer_usd_amount ) usd_cancel_fee += 1; int64_t core_create_fee = usd_create_fee * cer_core_amount / cer_usd_amount; + int64_t core_update_fee = usd_update_fee * cer_core_amount / cer_usd_amount; int64_t core_cancel_fee = usd_cancel_fee * cer_core_amount / cer_usd_amount; BOOST_CHECK( core_cancel_fee >= order_cancel_fee ); @@ -1857,7 +1937,8 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) // prepare params uint32_t blocks_generated = 0; time_point_sec max_exp = time_point_sec::maximum(); - time_point_sec exp = db.head_block_time(); // order will be accepted when pushing trx then expired at current block + time_point_sec exp = db.head_block_time(); // order will be accepted when pushing trx then + // expired at current block price cer = usd_id( db ).options.core_exchange_rate; const auto* usd_stat = &usd_id( db ).dynamic_asset_data_id( db ); @@ -1904,6 +1985,15 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) accum_on_fill = 0; pool_refund = 0; } + // refund data related to order update + int64_t core_update_fee_refund_core = order_update_fee; + int64_t core_update_fee_refund_usd = 0; + int64_t usd_update_fee_refund_core = 0; + int64_t usd_update_fee_refund_usd = usd_update_fee; + int64_t accum_on_fill_after_update = usd_update_fee; + int64_t pool_refund_after_update = core_update_fee; + + int64_t update_net_fee = order_cancel_fee * order_update_fee / order_create_fee; // Check non-overlapping // Alice creates order @@ -1922,6 +2012,27 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) BOOST_CHECK_EQUAL( usd_stat->fee_pool.value, pool_b ); BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value, accum_b ); + // Alice updates order + if( after_core_hf1604 ) + { + BOOST_TEST_MESSAGE( "Update order ao1" ); + update_limit_order( ao1_id, {}, asset(100), {}, cer ); + + alice_bc -= 100; // delta + alice_bu -= usd_update_fee; // fee + pool_b -= core_update_fee; // pool fee + + alice_bc += order_create_fee; // refund + alice_bc -= std::min( order_create_fee, update_net_fee ); // charge a fee (adjusted cancel_fee, capped) + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_bc ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_bu ); + BOOST_CHECK_EQUAL( usd_stat->fee_pool.value, pool_b ); + BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value, accum_b ); + } + // Alice cancels order if( !expire_order ) { @@ -1951,25 +2062,55 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) transfer( account_id_type(), bob_id, asset( bob_bu, usd_id) ); } - - if( !expire_order ) - alice_bc -= order_cancel_fee; // manual cancellation always need a fee - else if( before_hardfork_445 ) - { // do nothing: before hard fork 445, no fee on expired order - } - else if( !after_bsip26 ) + if( after_core_hf1604 ) { - // charge a cancellation fee in core, capped by deffered_fee which is order_create_fee - alice_bc -= std::min( order_cancel_fee, order_create_fee ); + if( !expire_order ) + alice_bc -= order_cancel_fee; // manual cancellation always need a fee + else // bsip26 + { + // when expired, should have core_update_fee in deferred, usd_update_fee in deferred_paid + + // charge a cancellation fee in core from fee_pool, capped by deffered + int64_t capped_core_cancel_fee = std::min( order_cancel_fee, core_update_fee ); + pool_b -= capped_core_cancel_fee; + + // charge a coresponding cancellation fee in usd from deffered_paid, round up, capped + int64_t capped_usd_cancel_fee = capped_core_cancel_fee * usd_update_fee / core_update_fee; + if( capped_usd_cancel_fee * core_update_fee != capped_core_cancel_fee * usd_update_fee ) + capped_usd_cancel_fee += 1; + if( capped_usd_cancel_fee > usd_update_fee ) + capped_usd_cancel_fee = usd_update_fee; + alice_bu -= capped_usd_cancel_fee; + + // cancellation fee goes to accumulated fees + accum_b += capped_usd_cancel_fee; + } + alice_bc += 100; // delta + alice_bc += usd_update_fee_refund_core; + alice_bu += usd_update_fee_refund_usd; + pool_b += pool_refund_after_update; } - else // bsip26 + else { - // charge a cancellation fee in core, capped by deffered_fee which is order_create_fee - alice_bc -= std::min( order_cancel_fee, order_create_fee ); + if( !expire_order ) + alice_bc -= order_cancel_fee; // manual cancellation always need a fee + else if( before_hardfork_445 ) + { // do nothing: before hard fork 445, no fee on expired order + } + else if( !after_bsip26 ) + { + // charge a cancellation fee in core, capped by deffered_fee which is order_create_fee + alice_bc -= std::min( order_cancel_fee, order_create_fee ); + } + else // bsip26 + { + // charge a cancellation fee in core, capped by deffered_fee which is order_create_fee + alice_bc -= std::min( order_cancel_fee, order_create_fee ); + } + alice_bc += core_fee_refund_core; + alice_bu += core_fee_refund_usd; } alice_bc += 1000; - alice_bc += core_fee_refund_core; - alice_bu += core_fee_refund_usd; BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); @@ -1994,6 +2135,44 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) BOOST_CHECK_EQUAL( usd_stat->fee_pool.value, pool_b ); BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value, accum_b ); + // Bob updates order + if( after_core_hf1604 ) + { + BOOST_TEST_MESSAGE( "Update order bo1" ); + update_limit_order( bo1_id, {}, asset(100, usd_id) ); + + bob_bu -= 100; // delta + bob_bc -= order_update_fee; // fee + + // there should have core_create_fee in deferred, usd_create_fee in deferred_paid + + // refund + pool_b += core_create_fee; + bob_bu += usd_create_fee; + + // charge an adjusted cancellation fee in core from fee_pool, capped by deffered + int64_t capped_core_cancel_fee = std::min( update_net_fee, core_create_fee ); + pool_b -= capped_core_cancel_fee; + + // charge a coresponding cancellation fee in usd from deffered_paid, round up, capped + int64_t capped_usd_cancel_fee = capped_core_cancel_fee * usd_create_fee / core_create_fee; + if( capped_usd_cancel_fee * core_create_fee != capped_core_cancel_fee * usd_create_fee ) + capped_usd_cancel_fee += 1; + if( capped_usd_cancel_fee > usd_create_fee ) + capped_usd_cancel_fee = usd_create_fee; + bob_bu -= capped_usd_cancel_fee; + + // cancellation fee goes to accumulated fees + accum_b += capped_usd_cancel_fee; + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_bc ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_bu ); + BOOST_CHECK_EQUAL( usd_stat->fee_pool.value, pool_b ); + BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value, accum_b ); + } + // Bob cancels order if( !expire_order ) { @@ -2023,22 +2202,93 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) transfer( account_id_type(), bob_id, asset( bob_bu, usd_id) ); } - if( !expire_order ) - bob_bc -= order_cancel_fee; // manual cancellation always need a fee - else if( before_hardfork_445 ) - { // do nothing: before hard fork 445, no fee on expired order + if( after_core_hf1604 ) + { + if( !expire_order ) + bob_bc -= order_cancel_fee; // manual cancellation always need a fee + else + { + // charge a cancellation fee in core, capped by deffered_fee which is order_update_fee + bob_bc -= std::min( order_cancel_fee, order_update_fee ); + } + bob_bu += 100; // delta + bob_bc += core_update_fee_refund_core; + bob_bu += core_update_fee_refund_usd; } - else if( !after_bsip26 ) + else { - // charge a cancellation fee in core, capped by deffered_fee which is core_create_fee - bob_bc -= std::min( order_cancel_fee, core_create_fee ); + if( !expire_order ) + bob_bc -= order_cancel_fee; // manual cancellation always need a fee + else if( before_hardfork_445 ) + { // do nothing: before hard fork 445, no fee on expired order + } + else if( !after_bsip26 ) + { + // charge a cancellation fee in core, capped by deffered_fee which is core_create_fee + bob_bc -= std::min( order_cancel_fee, core_create_fee ); + } + else // bsip26 + { + // when expired, should have core_create_fee in deferred, usd_create_fee in deferred_paid + + // charge a cancellation fee in core from fee_pool, capped by deffered + int64_t capped_core_cancel_fee = std::min( order_cancel_fee, core_create_fee ); + pool_b -= capped_core_cancel_fee; + + // charge a coresponding cancellation fee in usd from deffered_paid, round up, capped + int64_t capped_usd_cancel_fee = capped_core_cancel_fee * usd_create_fee / core_create_fee; + if( capped_usd_cancel_fee * core_create_fee != capped_core_cancel_fee * usd_create_fee ) + capped_usd_cancel_fee += 1; + if( capped_usd_cancel_fee > usd_create_fee ) + capped_usd_cancel_fee = usd_create_fee; + bob_bu -= capped_usd_cancel_fee; + + // cancellation fee goes to accumulated fees + accum_b += capped_usd_cancel_fee; + } + bob_bc += usd_fee_refund_core; + bob_bu += usd_fee_refund_usd; + pool_b += pool_refund; // bo1 } - else // bsip26 + bob_bu += 500; + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_bc ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_bu ); + BOOST_CHECK_EQUAL( usd_stat->fee_pool.value, pool_b ); + BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value, accum_b ); + + + // Check partial fill + BOOST_TEST_MESSAGE( "Creating ao2" ); + const limit_order_object* ao2 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), exp, cer ); + const limit_order_id_type ao2_id = ao2->get_id(); + + // data after order created + alice_bc -= 1000; + alice_bu -= usd_create_fee; + pool_b -= core_create_fee; + accum_b += accum_on_new; + + // Alice updates order + if( after_core_hf1604 ) { - // when expired, should have core_create_fee in deferred, usd_create_fee in deferred_paid + BOOST_TEST_MESSAGE( "Update order ao2" ); + update_limit_order( ao2_id, {}, asset(100), {}, cer ); + + alice_bc -= 100; // delta + alice_bu -= usd_update_fee; // fee + pool_b -= core_update_fee; // pool fee + + // there should have core_create_fee in deferred, usd_create_fee in deferred_paid + + // refund + pool_b += core_create_fee; + alice_bu += usd_create_fee; - // charge a cancellation fee in core from fee_pool, capped by deffered - int64_t capped_core_cancel_fee = std::min( order_cancel_fee, core_create_fee ); + // charge an adjusted cancellation fee in core from fee_pool, capped by deffered + int64_t capped_core_cancel_fee = std::min( update_net_fee, core_create_fee ); pool_b -= capped_core_cancel_fee; // charge a coresponding cancellation fee in usd from deffered_paid, round up, capped @@ -2047,45 +2297,75 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) capped_usd_cancel_fee += 1; if( capped_usd_cancel_fee > usd_create_fee ) capped_usd_cancel_fee = usd_create_fee; - bob_bu -= capped_usd_cancel_fee; + alice_bu -= capped_usd_cancel_fee; // cancellation fee goes to accumulated fees accum_b += capped_usd_cancel_fee; + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_bc ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_bu ); + BOOST_CHECK_EQUAL( usd_stat->fee_pool.value, pool_b ); + BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value, accum_b ); } - bob_bc += usd_fee_refund_core; - bob_bu += 500; - bob_bu += usd_fee_refund_usd; - pool_b += pool_refund; // bo1 - BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); - BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); - BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_bc ); - BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_bu ); - BOOST_CHECK_EQUAL( usd_stat->fee_pool.value, pool_b ); - BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value, accum_b ); + // Alice updates order again + if( after_core_hf1604 ) + { + BOOST_TEST_MESSAGE( "Update order ao2 again" ); + update_limit_order( ao2_id, {}, asset(100), {}, cer ); + alice_bc -= 100; // delta + alice_bu -= usd_update_fee; // fee + pool_b -= core_update_fee; // pool fee - // Check partial fill - BOOST_TEST_MESSAGE( "Creating ao2, then be partially filled by bo2" ); - const limit_order_object* ao2 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), exp, cer ); - const limit_order_id_type ao2id = ao2->get_id(); + // there should have core_update_fee in deferred, usd_update_fee in deferred_paid + + // refund + pool_b += core_update_fee; + alice_bu += usd_update_fee; + + // charge an adjusted cancellation fee in core from fee_pool, capped by deffered + int64_t capped_core_cancel_fee = std::min( update_net_fee, core_update_fee ); + pool_b -= capped_core_cancel_fee; + + // charge a coresponding cancellation fee in usd from deffered_paid, round up, capped + int64_t capped_usd_cancel_fee = capped_core_cancel_fee * usd_update_fee / core_update_fee; + if( capped_usd_cancel_fee * core_update_fee != capped_core_cancel_fee * usd_update_fee ) + capped_usd_cancel_fee += 1; + if( capped_usd_cancel_fee > usd_update_fee ) + capped_usd_cancel_fee = usd_update_fee; + alice_bu -= capped_usd_cancel_fee; + + // cancellation fee goes to accumulated fees + accum_b += capped_usd_cancel_fee; + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_bc ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_bu ); + BOOST_CHECK_EQUAL( usd_stat->fee_pool.value, pool_b ); + BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value, accum_b ); + } + + BOOST_TEST_MESSAGE( "Creating bo2, partially fill ao2" ); const limit_order_object* bo2 = create_sell_order( bob_id, asset(100, usd_id), asset(500) ); - BOOST_CHECK( db.find( ao2id ) != nullptr ); + BOOST_CHECK( db.find( ao2_id ) != nullptr ); BOOST_CHECK( bo2 == nullptr ); // data after order created - alice_bc -= 1000; - alice_bu -= usd_create_fee; - pool_b -= core_create_fee; - accum_b += accum_on_new; bob_bc -= order_create_fee; bob_bu -= 100; // data after order filled alice_bu += 100; bob_bc += 500; - accum_b += accum_on_fill; // ao2 + if( after_core_hf1604 ) + accum_b += accum_on_fill_after_update; // ao2 + else + accum_b += accum_on_fill; // ao2 BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); @@ -2130,6 +2410,8 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) // before hard fork 445, no fee when order is expired; // after hard fork 445, when partially filled order expired, order cancel fee is capped at 0 alice_bc += 500; + if( after_core_hf1604 ) + alice_bc += 200; // delta * 2 BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); @@ -2141,11 +2423,11 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) // Check multiple fill // Alice creating multiple orders BOOST_TEST_MESSAGE( "Creating ao31-ao35" ); - const limit_order_object* ao31 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer ); - const limit_order_object* ao32 = create_sell_order( alice_id, asset(1000), asset(2000, usd_id), max_exp, cer ); - const limit_order_object* ao33 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer ); - const limit_order_object* ao34 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer ); - const limit_order_object* ao35 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer ); + const limit_order_object* ao31 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer); + const limit_order_object* ao32 = create_sell_order( alice_id, asset(1000), asset(2000,usd_id), max_exp, cer); + const limit_order_object* ao33 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer); + const limit_order_object* ao34 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer); + const limit_order_object* ao35 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer); const limit_order_id_type ao31id = ao31->get_id(); const limit_order_id_type ao32id = ao32->get_id(); @@ -2338,17 +2620,17 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) fee_parameters::flat_set_type new_fees; { - limit_order_create_operation::fee_parameters_type create_fee_params; + limit_order_create_operation::fee_params_t create_fee_params; create_fee_params.fee = order_create_fee; new_fees.insert( create_fee_params ); } { - limit_order_cancel_operation::fee_parameters_type cancel_fee_params; + limit_order_cancel_operation::fee_params_t cancel_fee_params; cancel_fee_params.fee = order_cancel_fee; new_fees.insert( cancel_fee_params ); } { - transfer_operation::fee_parameters_type transfer_fee_params; + transfer_operation::fee_params_t transfer_fee_params; transfer_fee_params.fee = 0; transfer_fee_params.price_per_kbyte = 0; new_fees.insert( transfer_fee_params ); @@ -3640,13 +3922,21 @@ BOOST_AUTO_TEST_CASE( stealth_fba_test ) BOOST_AUTO_TEST_CASE( defaults_test ) { try { fee_schedule schedule; - const limit_order_create_operation::fee_parameters_type default_order_fee {}; + const limit_order_create_operation::fee_params_t default_order_fee {}; // no fees set yet -> default asset fee = schedule.calculate_fee( limit_order_create_operation() ); BOOST_CHECK_EQUAL( (int64_t)default_order_fee.fee, fee.amount.value ); - limit_order_create_operation::fee_parameters_type new_order_fee; new_order_fee.fee = 123; + // fill_order fee is zero + fee = schedule.calculate_fee( fill_order_operation() ); + BOOST_CHECK_EQUAL( (int64_t)0, fee.amount.value ); + + // execute_bid fee is zero + fee = schedule.calculate_fee( execute_bid_operation() ); + BOOST_CHECK_EQUAL( (int64_t)0, fee.amount.value ); + + limit_order_create_operation::fee_params_t new_order_fee; new_order_fee.fee = 123; // set fee + check schedule.parameters.insert( new_order_fee ); fee = schedule.calculate_fee( limit_order_create_operation() ); @@ -3654,8 +3944,8 @@ BOOST_AUTO_TEST_CASE( defaults_test ) // bid_collateral fee defaults to call_order_update fee // call_order_update fee is unset -> default - const call_order_update_operation::fee_parameters_type default_short_fee {}; - call_order_update_operation::fee_parameters_type new_short_fee; new_short_fee.fee = 123; + const call_order_update_operation::fee_params_t default_short_fee {}; + call_order_update_operation::fee_params_t new_short_fee; new_short_fee.fee = 123; fee = schedule.calculate_fee( bid_collateral_operation() ); BOOST_CHECK_EQUAL( (int64_t)default_short_fee.fee, fee.amount.value ); @@ -3665,10 +3955,19 @@ BOOST_AUTO_TEST_CASE( defaults_test ) BOOST_CHECK_EQUAL( (int64_t)new_short_fee.fee, fee.amount.value ); // set bid_collateral fee + check - bid_collateral_operation::fee_parameters_type new_bid_fee; new_bid_fee.fee = 124; + bid_collateral_operation::fee_params_t new_bid_fee; new_bid_fee.fee = 124; schedule.parameters.insert( new_bid_fee ); fee = schedule.calculate_fee( bid_collateral_operation() ); BOOST_CHECK_EQUAL( (int64_t)new_bid_fee.fee, fee.amount.value ); + + // fill_order fee is still zero + fee = schedule.calculate_fee( fill_order_operation() ); + BOOST_CHECK_EQUAL( (int64_t)0, fee.amount.value ); + + // execute_bid fee is still zero + fee = schedule.calculate_fee( execute_bid_operation() ); + BOOST_CHECK_EQUAL( (int64_t)0, fee.amount.value ); + } catch( const fc::exception& e ) { @@ -3681,7 +3980,7 @@ BOOST_AUTO_TEST_CASE( sub_asset_creation_fee_test ) { try { fee_schedule schedule; - asset_create_operation::fee_parameters_type default_ac_fee; + asset_create_operation::fee_params_t default_ac_fee; asset_create_operation op; op.symbol = "TEST.SUB"; @@ -3697,7 +3996,7 @@ BOOST_AUTO_TEST_CASE( sub_asset_creation_fee_test ) BOOST_CHECK_EQUAL( fee.amount.value, expected_fee ); // set fee + check - asset_create_operation::fee_parameters_type ac_fee; + asset_create_operation::fee_params_t ac_fee; ac_fee.long_symbol = 100100; ac_fee.symbol4 = 2000200; ac_fee.symbol3 = 30000300; @@ -3713,7 +4012,7 @@ BOOST_AUTO_TEST_CASE( sub_asset_creation_fee_test ) // set fee for account_transfer_operation, no change on asset creation fee BOOST_TEST_MESSAGE("Testing our fee schedule without sub-asset creation fee enabled"); - account_transfer_operation::fee_parameters_type at_fee; + account_transfer_operation::fee_params_t at_fee; at_fee.fee = 5500; schedule.parameters.insert( at_fee ); @@ -3723,7 +4022,7 @@ BOOST_AUTO_TEST_CASE( sub_asset_creation_fee_test ) // enable sub-asset creation fee BOOST_TEST_MESSAGE("Testing our fee schedule with sub-asset creation fee enabled"); - schedule.parameters.insert( ticket_create_operation::fee_parameters_type() ); + schedule.parameters.insert( ticket_create_operation::fee_params_t() ); expected_fee = at_fee.fee + expected_data_fee; @@ -3907,4 +4206,97 @@ BOOST_AUTO_TEST_CASE( issue_433_indirect_test ) } } +BOOST_AUTO_TEST_CASE( fee_change_test ) +{ try { + + // At the beginning, the fee schedule is all zero + const auto& check_zero_fees = [&]() { + BOOST_REQUIRE( db.get_global_properties().parameters.current_fees ); + BOOST_CHECK_EQUAL( db.get_global_properties().parameters.current_fees->scale, 0 ); + BOOST_REQUIRE( !db.get_global_properties().parameters.current_fees->parameters.empty() ); + BOOST_CHECK_EQUAL( db.get_global_properties().parameters.current_fees->parameters.begin()->get< + transfer_operation::fee_params_t>().fee, 0 ); + }; + + check_zero_fees(); + + { + // Try to set default fees, it should fail, because some operations aren't available + proposal_create_operation cop = proposal_create_operation::committee_proposal( + db.get_global_properties().parameters, db.head_block_time() ); + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10; + committee_member_update_global_parameters_operation cmuop; + cmuop.new_parameters = db.get_global_properties().parameters; + cmuop.new_parameters.get_mutable_fees() = fee_schedule::get_default(); + cop.proposed_ops.emplace_back( cmuop ); + trx.operations.push_back( cop ); + + // It should fail + GRAPHENE_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + trx.clear(); + } + + // The fee schedule is still all zero + check_zero_fees(); + + // Proceed to a time in the far future + generate_blocks( fc::time_point_sec::maximum() - 86400 + - db.get_global_properties().parameters.maximum_proposal_lifetime ); + set_expiration( db, trx ); + + // The fee schedule is still all zero + check_zero_fees(); + + { + // Try to set default fees, it should succeed + proposal_create_operation cop = proposal_create_operation::committee_proposal( + db.get_global_properties().parameters, db.head_block_time() ); + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10; + committee_member_update_global_parameters_operation cmuop; + cmuop.new_parameters = db.get_global_properties().parameters; + cmuop.new_parameters.get_mutable_fees() = fee_schedule::get_default(); + cop.proposed_ops.emplace_back( cmuop ); + trx.operations.push_back( cop ); + + // It should succeed + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + proposal_id_type prop_id { ptx.operation_results[0].get() }; + + // The fee schedule is still all zero + check_zero_fees(); + + // Approve the proposal + proposal_update_operation uop; + uop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + uop.active_approvals_to_add = { get_account("init0").get_id(), get_account("init1").get_id(), + get_account("init2").get_id(), get_account("init3").get_id(), + get_account("init4").get_id(), get_account("init5").get_id(), + get_account("init6").get_id(), get_account("init7").get_id() }; + trx.operations.push_back(uop); + PUSH_TX(db, trx, ~0); + + // The fee schedule is still all zero + check_zero_fees(); + + generate_blocks( prop_id( db ).expiration_time + 5 ); + generate_blocks( db.get_dynamic_global_properties().next_maintenance_time ); + generate_block(); + + // The fee schedule is no longer all zero + BOOST_REQUIRE( db.get_global_properties().parameters.current_fees ); + BOOST_CHECK_EQUAL( db.get_global_properties().parameters.current_fees->scale, GRAPHENE_100_PERCENT ); + BOOST_REQUIRE( !db.get_global_properties().parameters.current_fees->parameters.empty() ); + BOOST_CHECK_EQUAL( db.get_global_properties().parameters.current_fees->parameters.begin()->get< + transfer_operation::fee_params_t>().fee, transfer_operation::fee_params_t().fee ); + BOOST_CHECK( transfer_operation::fee_params_t().fee != 0 ); + + idump( (db.get_global_properties()) ); + + } + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index 6b2023e75d..8f2095202a 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -1203,7 +1203,7 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) share_type settle4_refund = 5; // blackswan event occurs - BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( usd_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( db.find( call_id ) == nullptr ); BOOST_CHECK( db.find( call2_id ) == nullptr ); BOOST_CHECK( db.find( call3_id ) == nullptr ); @@ -1453,7 +1453,7 @@ BOOST_AUTO_TEST_CASE(call_settle_limit_settle) BOOST_CHECK_EQUAL( 40000, call3_id(db).collateral.value ); // blackswan event did not occur - BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !usd_id(db).bitasset_data(db).is_globally_settled() ); // check balances BOOST_CHECK_EQUAL( 0, get_balance(seller_id, usd_id) ); diff --git a/tests/tests/history_api_tests.cpp b/tests/tests/history_api_tests.cpp index c1a5f31a75..4608e4b3a1 100644 --- a/tests/tests/history_api_tests.cpp +++ b/tests/tests/history_api_tests.cpp @@ -138,8 +138,8 @@ BOOST_AUTO_TEST_CASE(get_account_history) { } } -BOOST_AUTO_TEST_CASE(get_account_history_virtual_operation_test) { - try { +BOOST_AUTO_TEST_CASE(get_account_history_virtual_operation_test) +{ try { graphene::app::history_api hist_api(app); asset_id_type usd_id = create_user_issued_asset("USD").get_id(); @@ -164,11 +164,38 @@ BOOST_AUTO_TEST_CASE(get_account_history_virtual_operation_test) { BOOST_CHECK( histories.front().block_time == db.head_block_time() ); BOOST_CHECK( histories.front().is_virtual ); - } catch (fc::exception &e) { - edump((e.to_detail_string())); - throw; - } -} + // Create a limit order that expires in 300 seconds + create_sell_order( dan_id, asset(10, usd_id), asset(10), db.head_block_time() + 300 ); + + generate_block(); + fc::usleep(fc::milliseconds(100)); + + auto order_create_op_id = operation::tag::value; + + histories = hist_api.get_account_history("dan", operation_history_id_type(), + 100, operation_history_id_type()); + + BOOST_REQUIRE_GT( histories.size(), 0 ); + BOOST_CHECK_EQUAL( histories.front().op.which(), order_create_op_id ); + BOOST_CHECK( histories.front().block_time == db.head_block_time() ); + BOOST_CHECK( !histories.front().is_virtual ); + + // Let the limit order expire + generate_blocks( db.head_block_time() + 300 ); + generate_block(); + fc::usleep(fc::milliseconds(100)); + + auto order_cancel_op_id = operation::tag::value; + + histories = hist_api.get_account_history("dan", operation_history_id_type(), + 100, operation_history_id_type()); + + BOOST_REQUIRE_GT( histories.size(), 0 ); + BOOST_CHECK_EQUAL( histories.front().op.which(), order_cancel_op_id ); + BOOST_CHECK( histories.front().is_virtual ); + BOOST_CHECK( histories.front().result.is_type() ); + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE(get_account_history_notify_all_on_creation) { try { diff --git a/tests/tests/htlc_tests.cpp b/tests/tests/htlc_tests.cpp index 833b7feebf..8a844657d5 100644 --- a/tests/tests/htlc_tests.cpp +++ b/tests/tests/htlc_tests.cpp @@ -803,7 +803,7 @@ BOOST_AUTO_TEST_CASE( htlc_hardfork_test ) .parameters.extensions.value.updatable_htlc_options->max_preimage_size, 2048u); const graphene::chain::fee_schedule& current_fee_schedule = *(db.get_global_properties().parameters.current_fees); - const htlc_create_operation::fee_parameters_type& htlc_fee + const htlc_create_operation::fee_params_t& htlc_fee = current_fee_schedule.get(); BOOST_CHECK_EQUAL(htlc_fee.fee, 2 * GRAPHENE_BLOCKCHAIN_PRECISION); @@ -904,7 +904,7 @@ BOOST_AUTO_TEST_CASE( fee_calculations ) { // create { - htlc_create_operation::fee_parameters_type create_fee; + htlc_create_operation::fee_params_t create_fee; create_fee.fee = 2; create_fee.fee_per_day = 2; htlc_create_operation create; @@ -920,7 +920,7 @@ BOOST_AUTO_TEST_CASE( fee_calculations ) } // redeem { - htlc_redeem_operation::fee_parameters_type redeem_fee; + htlc_redeem_operation::fee_params_t redeem_fee; redeem_fee.fee_per_kb = 2; redeem_fee.fee = 2; htlc_redeem_operation redeem; @@ -938,7 +938,7 @@ BOOST_AUTO_TEST_CASE( fee_calculations ) } // extend { - htlc_extend_operation::fee_parameters_type extend_fee; + htlc_extend_operation::fee_params_t extend_fee; extend_fee.fee = 2; extend_fee.fee_per_day = 2; htlc_extend_operation extend; diff --git a/tests/tests/liquidity_pool_tests.cpp b/tests/tests/liquidity_pool_tests.cpp index d0c9d1c085..7f437d7970 100644 --- a/tests/tests/liquidity_pool_tests.cpp +++ b/tests/tests/liquidity_pool_tests.cpp @@ -94,6 +94,164 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_hardfork_time_test ) } } +BOOST_AUTO_TEST_CASE( liquidity_pool_update_hardfork_time_test ) +{ + try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME ); + set_expiration( db, trx ); + + ACTORS((sam)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + + const asset_object& core = asset_id_type()(db); + const asset_object& usd = create_user_issued_asset( "MYUSD" ); + const asset_object& lpa = create_user_issued_asset( "LPATEST", sam, charge_market_fee ); + + const liquidity_pool_object& lpo = create_liquidity_pool( sam_id, core.get_id(), usd.get_id(), lpa.get_id(), + 0, 0 ); + + // Before the hard fork, unable to update a liquidity pool + // or update with proposals + BOOST_CHECK_THROW( update_liquidity_pool( sam_id, lpo.get_id(), 1, 0 ), fc::exception ); + + liquidity_pool_update_operation updop = make_liquidity_pool_update_op( sam_id, lpo.get_id(), 1, 0 ); + BOOST_CHECK_THROW( propose( updop ), fc::exception ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( liquidity_pool_update_test ) +{ + try { + + // Pass the hard fork time + generate_blocks( HARDFORK_CORE_2604_TIME ); + set_expiration( db, trx ); + + ACTORS((sam)(ted)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( ted, asset(init_amount) ); + + const asset_object& core = asset_id_type()(db); + const asset_object& usd = create_user_issued_asset( "MYUSD" ); + issue_uia( sam, usd.amount(init_amount) ); + issue_uia( ted, usd.amount(init_amount) ); + + const asset_object& lpa1 = create_user_issued_asset( "LPATEST1", sam, charge_market_fee ); + const asset_object& lpa2 = create_user_issued_asset( "LPATEST2", ted, charge_market_fee ); + + const liquidity_pool_object& lpo1 = create_liquidity_pool( sam_id, core.get_id(), usd.get_id(), lpa1.get_id(), + 0, 0 ); + + BOOST_CHECK( lpo1.asset_a == core.id ); + BOOST_CHECK( lpo1.asset_b == usd.id ); + BOOST_CHECK( lpo1.balance_a == 0 ); + BOOST_CHECK( lpo1.balance_b == 0 ); + BOOST_CHECK( lpo1.share_asset == lpa1.id ); + BOOST_CHECK( lpo1.taker_fee_percent == 0 ); + BOOST_CHECK( lpo1.withdrawal_fee_percent == 0 ); + BOOST_CHECK( lpo1.virtual_value == 0 ); + + deposit_to_liquidity_pool( sam_id, lpo1.get_id(), asset(10), asset( 20, usd.get_id() ) ); + + BOOST_CHECK( lpo1.asset_a == core.id ); + BOOST_CHECK( lpo1.asset_b == usd.id ); + BOOST_CHECK( lpo1.balance_a == 10 ); + BOOST_CHECK( lpo1.balance_b == 20 ); + BOOST_CHECK( lpo1.share_asset == lpa1.id ); + BOOST_CHECK( lpo1.taker_fee_percent == 0 ); + BOOST_CHECK( lpo1.withdrawal_fee_percent == 0 ); + BOOST_CHECK( lpo1.virtual_value == 200 ); + + const liquidity_pool_object& lpo2 = create_liquidity_pool( ted_id, core.get_id(), usd.get_id(), lpa2.get_id(), + 1, 2 ); + + BOOST_CHECK( lpo2.asset_a == core.id ); + BOOST_CHECK( lpo2.asset_b == usd.id ); + BOOST_CHECK( lpo2.balance_a == 0 ); + BOOST_CHECK( lpo2.balance_b == 0 ); + BOOST_CHECK( lpo2.share_asset == lpa2.id ); + BOOST_CHECK( lpo2.taker_fee_percent == 1 ); + BOOST_CHECK( lpo2.withdrawal_fee_percent == 2 ); + BOOST_CHECK( lpo2.virtual_value == 0 ); + + // Able to propose + { + liquidity_pool_update_operation updop = make_liquidity_pool_update_op( sam_id, lpo1.get_id(), 1, 0 ); + propose( updop ); + } + + // Unable to update a liquidity pool with invalid data + // update nothing + BOOST_CHECK_THROW( update_liquidity_pool( sam_id, lpo1.get_id(), {}, {} ), fc::exception ); + BOOST_CHECK_THROW( propose( make_liquidity_pool_update_op( sam_id, lpo1.get_id(), {}, {} ) ), fc::exception ); + // non-zero withdrawal fee + BOOST_CHECK_THROW( update_liquidity_pool( sam_id, lpo1.get_id(), {}, 1 ), fc::exception ); + BOOST_CHECK_THROW( propose( make_liquidity_pool_update_op( sam_id, lpo1.get_id(), {}, 1 ) ), fc::exception ); + BOOST_CHECK_THROW( update_liquidity_pool( sam_id, lpo1.get_id(), 0, 1 ), fc::exception ); + BOOST_CHECK_THROW( propose( make_liquidity_pool_update_op( sam_id, lpo1.get_id(), 0, 1 ) ), fc::exception ); + // taker fee exceeds 100% + BOOST_CHECK_THROW( update_liquidity_pool( sam_id, lpo1.get_id(), 10001, {} ), fc::exception ); + BOOST_CHECK_THROW( update_liquidity_pool( sam_id, lpo1.get_id(), 10001, 0 ), fc::exception ); + BOOST_CHECK_THROW( propose( make_liquidity_pool_update_op( sam_id, lpo1.get_id(), 10001, {} ) ), fc::exception); + BOOST_CHECK_THROW( propose( make_liquidity_pool_update_op( sam_id, lpo1.get_id(), 10001, 0 ) ), fc::exception ); + // Owner mismatch (able to propose) + BOOST_CHECK_THROW( update_liquidity_pool( ted_id, lpo1.get_id(), 1, {} ), fc::exception ); + propose( make_liquidity_pool_update_op( ted_id, lpo1.get_id(), 1, {} ) ); + // Updating taker fee when withdrawal fee is non-zero (able to propose) + BOOST_CHECK_THROW( update_liquidity_pool( ted_id, lpo2.get_id(), 1, {} ), fc::exception ); + propose( make_liquidity_pool_update_op( ted_id, lpo2.get_id(), 1, {} ) ); + + // Sam is able to update lpo1 + update_liquidity_pool( sam_id, lpo1.get_id(), 2, 0 ); + BOOST_CHECK( lpo1.asset_a == core.id ); + BOOST_CHECK( lpo1.asset_b == usd.id ); + BOOST_CHECK( lpo1.balance_a == 10 ); + BOOST_CHECK( lpo1.balance_b == 20 ); + BOOST_CHECK( lpo1.share_asset == lpa1.id ); + BOOST_CHECK( lpo1.taker_fee_percent == 2 ); + BOOST_CHECK( lpo1.withdrawal_fee_percent == 0 ); + BOOST_CHECK( lpo1.virtual_value == 200 ); + + update_liquidity_pool( sam_id, lpo1.get_id(), 1, {} ); + BOOST_CHECK( lpo1.asset_a == core.id ); + BOOST_CHECK( lpo1.asset_b == usd.id ); + BOOST_CHECK( lpo1.balance_a == 10 ); + BOOST_CHECK( lpo1.balance_b == 20 ); + BOOST_CHECK( lpo1.share_asset == lpa1.id ); + BOOST_CHECK( lpo1.taker_fee_percent == 1 ); + BOOST_CHECK( lpo1.withdrawal_fee_percent == 0 ); + BOOST_CHECK( lpo1.virtual_value == 200 ); + + // Ted is able to update lpo2 if to update its withdrawal fee to 0 + update_liquidity_pool( ted_id, lpo2.get_id(), 2, 0 ); + + BOOST_CHECK( lpo2.asset_a == core.id ); + BOOST_CHECK( lpo2.asset_b == usd.id ); + BOOST_CHECK( lpo2.balance_a == 0 ); + BOOST_CHECK( lpo2.balance_b == 0 ); + BOOST_CHECK( lpo2.share_asset == lpa2.id ); + BOOST_CHECK( lpo2.taker_fee_percent == 2 ); + BOOST_CHECK( lpo2.withdrawal_fee_percent == 0 ); + BOOST_CHECK( lpo2.virtual_value == 0 ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_CASE( liquidity_pool_create_delete_proposal_test ) { try { diff --git a/tests/tests/margin_call_fee_tests.cpp b/tests/tests/margin_call_fee_tests.cpp index acb8592ea6..85ff2b0fd4 100644 --- a/tests/tests/margin_call_fee_tests.cpp +++ b/tests/tests/margin_call_fee_tests.cpp @@ -373,7 +373,7 @@ BOOST_FIXTURE_TEST_SUITE(margin_call_fee_tests, bitasset_database_fixture) ////// call_order_id_type bob_call_id = (*borrow(bob, bob_initial_smart, bob_initial_core)).get_id(); BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 200 * SMARTBIT_UNIT); - BOOST_CHECK(!smartbit.bitasset_data(db).has_settlement()); // No global settlement + BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement const price bob_initial_cr = bob_call_id(db).collateralization(); // Units of collateral / debt BOOST_CHECK_EQUAL(bob_initial_cr.base.amount.value, 80000000); // Collateral of 80,000,000 satoshi CORE BOOST_CHECK_EQUAL(bob_initial_cr.quote.amount.value, 2000000); // Debt of 2,000,000 satoshi SMARTBIT @@ -401,7 +401,7 @@ BOOST_FIXTURE_TEST_SUITE(margin_call_fee_tests, bitasset_database_fixture) publish_feed(smartbit, feedproducer_id(db), current_feed); BOOST_CHECK(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price); - BOOST_CHECK(!smartbit.bitasset_data(db).has_settlement()); // No global settlement + BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement // Check Bob's debt to the blockchain BOOST_CHECK_EQUAL(bob_call_id(db).debt.value, bob_initial_smart.amount.value); @@ -652,7 +652,7 @@ BOOST_FIXTURE_TEST_SUITE(margin_call_fee_tests, bitasset_database_fixture) ////// call_order_id_type bob_call_id = (*borrow(bob, bob_initial_smart, bob_initial_core)).get_id(); BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 200 * SMARTBIT_UNIT); - BOOST_CHECK(!smartbit.bitasset_data(db).has_settlement()); // No global settlement + BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement const price bob_initial_cr = bob_call_id(db).collateralization(); // Units of collateral / debt BOOST_CHECK_EQUAL(bob_initial_cr.base.amount.value, 80000000); // Collateral of 80,000,000 satoshi CORE BOOST_CHECK_EQUAL(bob_initial_cr.quote.amount.value, 2000000); // Debt of 2,000,000 satoshi SMARTBIT @@ -680,7 +680,7 @@ BOOST_FIXTURE_TEST_SUITE(margin_call_fee_tests, bitasset_database_fixture) publish_feed(smartbit, feedproducer_id(db), current_feed); BOOST_CHECK(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price); - BOOST_CHECK(!smartbit.bitasset_data(db).has_settlement()); // No global settlement + BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement // Check Bob's debt to the blockchain BOOST_CHECK_EQUAL(bob_call_id(db).debt.value, bob_initial_smart.amount.value); @@ -699,7 +699,7 @@ BOOST_FIXTURE_TEST_SUITE(margin_call_fee_tests, bitasset_database_fixture) // Charlie obtains his SMARTBIT by borrowing it from the blockchain call_order_id_type charlie_call_id = (*borrow(charlie, charlie_initial_smart, charlie_initial_core)).get_id(); BOOST_REQUIRE_EQUAL(get_balance(charlie, smartbit), 200 * SMARTBIT_UNIT); - BOOST_CHECK(!smartbit.bitasset_data(db).has_settlement()); // No global settlement + BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement const price charlie_initial_cr = charlie_call_id(db).collateralization(); // Units of collateral / debt BOOST_CHECK_EQUAL(charlie_initial_cr.base.amount.value, 120000000); // Collateral of 120,000,000 satoshi CORE BOOST_CHECK_EQUAL(charlie_initial_cr.quote.amount.value, 2000000); // Debt of 2,000,000 satoshi SMARTBIT @@ -974,7 +974,7 @@ BOOST_FIXTURE_TEST_SUITE(margin_call_fee_tests, bitasset_database_fixture) const uint16_t tcr = 2200; // Bob's target collateral ratio (TCR) 220% expressed in terms of GRAPHENE_COLLATERAL_RATIO_DENOM call_order_id_type bob_call_id = (*borrow(bob, bob_initial_smart, bob_initial_core, tcr)).get_id(); BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 200 * SMARTBIT_UNIT); - BOOST_CHECK(!smartbit.bitasset_data(db).has_settlement()); // No global settlement + BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement const price bob_initial_cr = bob_call_id(db).collateralization(); // Units of collateral / debt BOOST_CHECK_EQUAL(bob_initial_cr.base.amount.value, 80000000); // Collateral of 80,000,000 satoshi CORE BOOST_CHECK_EQUAL(bob_initial_cr.quote.amount.value, 2000000); // Debt of 2,000,000 satoshi SMARTBIT @@ -1002,7 +1002,7 @@ BOOST_FIXTURE_TEST_SUITE(margin_call_fee_tests, bitasset_database_fixture) publish_feed(smartbit, feedproducer_id(db), current_feed); BOOST_CHECK(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price); - BOOST_CHECK(!smartbit.bitasset_data(db).has_settlement()); // No global settlement + BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement // Check Bob's debt to the blockchain BOOST_CHECK_EQUAL(bob_call_id(db).debt.value, bob_initial_smart.amount.value); @@ -1343,7 +1343,7 @@ BOOST_FIXTURE_TEST_SUITE(margin_call_fee_tests, bitasset_database_fixture) call_order_id_type alice_call_id = (*borrow(alice, alice_initial_smart, alice_initial_core)).get_id(); BOOST_CHECK_EQUAL(get_balance(alice_id(db), smartbit_id(db)), 500 * SMARTBIT_UNIT); BOOST_CHECK_EQUAL(get_balance(alice_id, core_id), 0 * CORE_UNIT); - BOOST_CHECK(!smartbit.bitasset_data(db).has_settlement()); // No global settlement + BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement // Alice offer to sell the SMARTBIT // Create a "large" sell order at a "high" price of settlement_price * 1.1 = settlement_price * (11/10) @@ -1382,7 +1382,7 @@ BOOST_FIXTURE_TEST_SUITE(margin_call_fee_tests, bitasset_database_fixture) // Bobs's balances should reflect that CORE was used to create SMARTBIT BOOST_CHECK_EQUAL(get_balance(bob_id, smartbit_id), 200 * SMARTBIT_UNIT); BOOST_CHECK_EQUAL(get_balance(bob_id, core_id), 0); - BOOST_CHECK(!smartbit.bitasset_data(db).has_settlement()); // No global settlement + BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement const price bob_initial_cr = bob_call_id(db).collateralization(); // Units of collateral / debt BOOST_CHECK(bob_initial_cr == expected_bob_initial_cr); BOOST_CHECK_EQUAL(bob_initial_cr.base.amount.value, 80000000); // Collateral of 80,000,000 satoshi CORE @@ -1422,7 +1422,7 @@ BOOST_FIXTURE_TEST_SUITE(margin_call_fee_tests, bitasset_database_fixture) // Confirm the updated feed BOOST_CHECK(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price); // Confirm no global settlement - BOOST_CHECK(!smartbit.bitasset_data(db).has_settlement()); + BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // The margin call of Bob's position should have closed the debt of bob_initial_smart @@ -1641,7 +1641,7 @@ BOOST_FIXTURE_TEST_SUITE(margin_call_fee_tests, bitasset_database_fixture) borrow(alice, alice_initial_smart, alice_initial_core); BOOST_CHECK_EQUAL(get_balance(alice_id(db), smartbit_id(db)), 500 * SMARTBIT_UNIT); BOOST_CHECK_EQUAL(get_balance(alice_id, core_id), 0 * CORE_UNIT); - BOOST_CHECK(!smartbit.bitasset_data(db).has_settlement()); // No global settlement + BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement // Alice offer to sell the SMARTBIT // Create a "large" sell order at a "high" price of settlement_price * 1.1 = settlement_price * (11/10) @@ -1679,7 +1679,7 @@ BOOST_FIXTURE_TEST_SUITE(margin_call_fee_tests, bitasset_database_fixture) const uint16_t tcr = 2200; // Bob's target collateral ratio (TCR) 220% expressed in terms of GRAPHENE_COLLATERAL_RATIO_DENOM call_order_id_type bob_call_id = (*borrow(bob, bob_initial_smart, bob_initial_core, tcr)).get_id(); BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 200 * SMARTBIT_UNIT); - BOOST_CHECK(!smartbit.bitasset_data(db).has_settlement()); // No global settlement + BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement const price bob_initial_cr = bob_call_id(db).collateralization(); // Units of collateral / debt BOOST_CHECK_EQUAL(bob_initial_cr.base.amount.value, 80000000); // Collateral of 80,000,000 satoshi CORE BOOST_CHECK_EQUAL(bob_initial_cr.quote.amount.value, 2000000); // Debt of 2,000,000 satoshi SMARTBIT @@ -1709,7 +1709,7 @@ BOOST_FIXTURE_TEST_SUITE(margin_call_fee_tests, bitasset_database_fixture) // Confirm the updated feed BOOST_CHECK(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price); // Confirm no global settlement - BOOST_CHECK(!smartbit.bitasset_data(db).has_settlement()); + BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // When a TCR is set for a call order, the ideal is to not sell all of the collateral // but only enough collateral so that the remaining collateral and the remaining debt in the debt position @@ -2040,7 +2040,7 @@ BOOST_FIXTURE_TEST_SUITE(margin_call_fee_tests, bitasset_database_fixture) call_order_id_type alice_call_id = (*borrow(alice, alice_initial_smart, alice_initial_core)).get_id(); BOOST_CHECK_EQUAL(get_balance(alice_id(db), smartbit_id(db)), 500 * SMARTBIT_UNIT); BOOST_CHECK_EQUAL(get_balance(alice_id, core_id), 0 * CORE_UNIT); - BOOST_CHECK(!smartbit.bitasset_data(db).has_settlement()); // No global settlement + BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement // Alice offer to sell the SMARTBIT const asset alice_debt_to_sell = smartbit.amount(500 * SMARTBIT_UNIT); @@ -2085,7 +2085,7 @@ BOOST_FIXTURE_TEST_SUITE(margin_call_fee_tests, bitasset_database_fixture) // Bobs's balances should reflect that CORE was used to create SMARTBIT BOOST_CHECK_EQUAL(get_balance(bob_id, smartbit_id), 200 * SMARTBIT_UNIT); BOOST_CHECK_EQUAL(get_balance(bob_id, core_id), 0); - BOOST_CHECK(!smartbit.bitasset_data(db).has_settlement()); // No global settlement + BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // No global settlement const price bob_initial_cr = bob_call_id(db).collateralization(); // Units of collateral / debt BOOST_CHECK(bob_initial_cr == expected_bob_initial_cr); BOOST_CHECK_EQUAL(bob_initial_cr.base.amount.value, 80000000); // Collateral of 80,000,000 satoshi CORE @@ -2120,7 +2120,7 @@ BOOST_FIXTURE_TEST_SUITE(margin_call_fee_tests, bitasset_database_fixture) // Confirm the updated feed BOOST_CHECK(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price); // Confirm no global settlement - BOOST_CHECK(!smartbit.bitasset_data(db).has_settlement()); + BOOST_CHECK(!smartbit.bitasset_data(db).is_globally_settled()); // Verify the margin call order price is as planned BOOST_CHECK(smartbit_id(db).bitasset_data(db).current_feed.margin_call_order_price(initial_mcfr) == planned_initial_mcop); diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index aa0be76841..d15eb54fcb 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -186,7 +186,7 @@ BOOST_AUTO_TEST_CASE(issue_338_etc) // settlement price = 1/20, mssp = 1/22 // black swan event doesn't occur #649 - BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !usd_id(db).bitasset_data(db).is_globally_settled() ); // generate a block generate_block(); @@ -418,7 +418,7 @@ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) // settlement price = 1/16, mssp = 10/176 // black swan event will occur: #649 fixed - BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( usd_id(db).bitasset_data(db).is_globally_settled() ); // short positions will be closed BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( !db.find( call2_id ) ); @@ -958,7 +958,7 @@ BOOST_AUTO_TEST_CASE(hard_fork_338_cross_test) // settlement price = 1/16, mssp = 10/176 // due to sell_low, black swan won't occur - BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !usd_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK_EQUAL( 3000-1000-1007-7, get_balance(seller_id, usd_id) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); @@ -982,7 +982,7 @@ BOOST_AUTO_TEST_CASE(hard_fork_338_cross_test) // collateralization of call3 is (16000-56-64) / (1000-7-7) = 15880/986 = 16.1, it's > 16 but < 17.6 // although there is no sell order, it should trigger a black swan event right away, // because after hard fork new limit order won't trigger black swan event - BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( usd_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( !db.find( call3_id ) ); BOOST_CHECK( !db.find( call4_id ) ); @@ -1084,16 +1084,16 @@ BOOST_AUTO_TEST_CASE(hard_fork_649_cross_test) // settlement price = 1/20, mssp = 1/22 // due to #649, black swan won't occur - BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !usd_id(db).bitasset_data(db).is_globally_settled() ); // generate a block to include operations above generate_block(); - BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !usd_id(db).bitasset_data(db).is_globally_settled() ); // go over the hard fork, make sure feed doesn't expire generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // a black swan event should occur - BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( usd_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( !db.find( call2_id ) ); BOOST_CHECK( !db.find( call3_id ) ); @@ -1313,7 +1313,7 @@ BOOST_AUTO_TEST_CASE(mcfr_blackswan_test) // settlement price = 1/18, mssp = 10/198 // GS occurs even when there is a good sell order - BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( usd_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( !db.find( call2_id ) ); // GS price is 1/18, but the first call order has only 15000 thus capped @@ -1419,7 +1419,7 @@ BOOST_AUTO_TEST_CASE(mcfr_blackswan_test_after_hf_core_2481) // settlement price = 1/18, mssp = 10/198 // GS occurs even when there is a good sell order - BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( usd_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( !db.find( call2_id ) ); BOOST_CHECK( !db.find( call3_id ) ); @@ -1517,7 +1517,7 @@ BOOST_AUTO_TEST_CASE(gs_price_test) if( !hf2481 ) { // GS occurs - BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( usd_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( !db.find( call2_id ) ); // sell order did not change @@ -1526,7 +1526,7 @@ BOOST_AUTO_TEST_CASE(gs_price_test) else { // GS does not occur, call got filled - BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !usd_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( !db.find( call_id ) ); // sell order got half-filled @@ -1648,7 +1648,7 @@ BOOST_AUTO_TEST_CASE(mcfr_rounding_test) publish_feed( bitusd, feedproducer, feed2 ); // blackswan - BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( usd_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( !db.find( call2_id ) ); int64_t call_pays_to_fund = (15000 * 10 + 10) / 11; @@ -1696,7 +1696,7 @@ BOOST_AUTO_TEST_CASE(mcfr_rounding_test) generate_blocks( db.head_block_time() + fc::seconds(43200) ); // The first call order should have been filled - BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !usd_id(db).bitasset_data(db).is_globally_settled() ); BOOST_CHECK( !db.find( call_id ) ); BOOST_REQUIRE( db.find( call2_id ) ); diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 88f3d9e012..7b27fe0aa6 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -50,8 +51,8 @@ using namespace graphene::chain::test; BOOST_FIXTURE_TEST_SUITE( operation_tests, database_fixture ) BOOST_AUTO_TEST_CASE( feed_limit_logic_test ) -{ - try { +{ try { + asset usd(1000,asset_id_type(1)); asset core(1000,asset_id_type(0)); price_feed feed; @@ -74,15 +75,431 @@ BOOST_AUTO_TEST_CASE( feed_limit_logic_test ) BOOST_CHECK( usd * feed.maintenance_price() < usd * feed.max_short_squeeze_price() ); */ - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( limit_order_update_hardfork_time_test ) +{ try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_CORE_2362_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((nathan)); + + const auto& munee = create_user_issued_asset("MUNEE"); + + transfer(committee_account, nathan_id, asset(1500)); + + auto expiration = db.head_block_time() + 1000; + auto sell_price = price(asset(500), munee.amount(1000)); + limit_order_id_type order_id = create_sell_order(nathan, asset(500), munee.amount(1000), expiration)->get_id(); + + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 500); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + + // Cannot update order yet + sell_price.base = asset(499); + GRAPHENE_REQUIRE_THROW( update_limit_order(order_id, sell_price), fc::assert_exception ); + + // Cannot propose + limit_order_update_operation louop = make_limit_order_update_op( nathan_id, order_id, sell_price ); + BOOST_CHECK_THROW( propose( louop ), fc::exception ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(limit_order_update_test) +{ try { + + generate_blocks(HARDFORK_CORE_1604_TIME + 10); + set_expiration( db, trx ); + + ACTORS((nathan)(dan)); + + const auto& bitusd = create_bitasset("USDBIT", nathan_id); + const auto& munee = create_user_issued_asset("MUNEE"); + const auto& core = asset_id_type()(db); + + update_feed_producers(bitusd, {nathan_id}); + price_feed current_feed; + current_feed.settlement_price = bitusd.amount(200) / core.amount(100); + current_feed.maintenance_collateral_ratio = 1750; + publish_feed(bitusd, nathan, current_feed); + + transfer(committee_account, nathan_id, asset(1500)); + issue_uia(nathan, munee.amount(100)); + borrow(nathan_id, bitusd.amount(100), asset(500)); + + auto expiration = db.head_block_time() + 1000; + auto sell_price = price(asset(500), bitusd.amount(1000)); + limit_order_id_type order_id = create_sell_order(nathan, asset(500), bitusd.amount(1000), expiration)->get_id(); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 500); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + + // Cannot update order without changing anything + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id), fc::exception); + // Cannot update order to use inverted price assets + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(bitusd.amount(2), asset(1))), fc::exception); + // Cannot update order to use negative price + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(asset(-1), bitusd.amount(2))), fc::exception); + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(asset(1), bitusd.amount(-2))), fc::exception); + // Cannot update order to use different assets + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(bitusd.amount(2), munee.amount(1))), + fc::exception); + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(munee.amount(2), bitusd.amount(1))), + fc::exception); + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(asset(2), munee.amount(1))), fc::exception); + // Cannot update order to expire in the past + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, {}, db.head_block_time() - 10), fc::exception); + // Cannot update order with a zero delta + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, asset()), fc::exception); + // Cannot update order to add more funds than seller has + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, asset(501)), fc::exception); + // Cannot update order to remove more funds than order has + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, asset(-501)), fc::exception); + // Cannot update order to remove all funds in order + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, asset(-500)), fc::exception); + // Cannot update order to add or remove different kind of funds + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, bitusd.amount(50)), fc::exception); + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, bitusd.amount(-50)), fc::exception); + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, munee.amount(50)), fc::exception); + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, munee.amount(-50)), fc::exception); + + // Cannot update someone else's order + limit_order_update_operation louop = make_limit_order_update_op( dan_id, order_id, {}, asset(-1) ); + trx.operations.clear(); + trx.operations.push_back( louop ); + GRAPHENE_REQUIRE_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Can propose + propose( louop ); + + // Cannot update an order which does not exist + louop = make_limit_order_update_op( nathan_id, order_id + 1, {}, asset(-1) ); + trx.operations.clear(); + trx.operations.push_back( louop ); + GRAPHENE_REQUIRE_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Try changing price + sell_price.base = asset(501); + // Cannot update base amount in the new price to be more than the amount for sale + GRAPHENE_REQUIRE_THROW( update_limit_order(order_id, sell_price), fc::exception ); + sell_price.base = asset(500); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + sell_price.base = asset(499); + update_limit_order(order_id, sell_price); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + sell_price.base = asset(500); + sell_price.quote = bitusd.amount(999); + update_limit_order(order_id, sell_price); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + sell_price.quote = bitusd.amount(1000); + update_limit_order(order_id, sell_price); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + + // Try changing expiration + expiration += 50; + update_limit_order(order_id, {}, {}, expiration); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + // Cannot change expiration to a time in the past + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, {}, db.head_block_time() - 1 ), fc::exception); + + // Try adding funds + update_limit_order(order_id, {}, asset(50)); + BOOST_REQUIRE_EQUAL(order_id(db).amount_for_sale().amount.value, 550); + BOOST_REQUIRE_EQUAL(db.get_balance(nathan_id, core.get_id()).amount.value, 450); + + // Try removing funds + update_limit_order(order_id, {}, asset(-100)); + BOOST_REQUIRE_EQUAL(order_id(db).amount_for_sale().amount.value, 450); + BOOST_REQUIRE_EQUAL(db.get_balance(nathan_id, core.get_id()).amount.value, 550); + + // Try changing everything at once + expiration += 50; + sell_price.base = asset(499); + sell_price.quote = bitusd.amount(1001); + update_limit_order(order_id, sell_price, 50, expiration); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + BOOST_REQUIRE_EQUAL(order_id(db).amount_for_sale().amount.value, 500); + BOOST_REQUIRE_EQUAL(db.get_balance(nathan_id, core.get_id()).amount.value, 500); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( limit_order_update_asset_authorization_test ) +{ try { + + generate_blocks(HARDFORK_CORE_1604_TIME + 10); + set_expiration( db, trx ); + + ACTORS((nathan)(dan)(whitey)(blacky)); + + const auto& munee = create_user_issued_asset("MUNEE", dan, white_list ); + const auto& noomo = create_user_issued_asset("NOOMO", dan, white_list ); + + issue_uia(nathan, munee.amount(100)); + issue_uia(nathan, noomo.amount(100)); + + auto expiration = db.head_block_time() + 1000; + auto sell_price = price( munee.amount(50), noomo.amount(60) ); + limit_order_id_type order_id = create_sell_order(nathan, munee.amount(50), noomo.amount(60), expiration) + ->get_id(); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 50); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + + // Can update order + update_limit_order(order_id, {}, munee.amount(-1)); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 49); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + + // Make a whitelist + { + BOOST_TEST_MESSAGE( "Setting up whitelisting" ); + asset_update_operation uop; + uop.asset_to_update = munee.id; + uop.issuer = dan_id; + uop.new_options = munee.options; + // The whitelist is managed by Whitey + uop.new_options.whitelist_authorities.insert(whitey_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Upgrade Whitey so that he can manage the whitelist + upgrade_to_lifetime_member( whitey_id ); + + // Add Dan to the whitelist, but do not add others + account_whitelist_operation wop; + wop.authorizing_account = whitey_id; + wop.account_to_list = dan_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + + // Cannot update order + GRAPHENE_REQUIRE_THROW( update_limit_order(order_id, {}, munee.amount(-1)), fc::exception ); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 49); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + + // Add Nathan to the whitelist + wop.account_to_list = nathan_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + + // Can update order + update_limit_order(order_id, {}, munee.amount(-1)); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 48); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + } + + // Make a blacklist + { + BOOST_TEST_MESSAGE( "Setting up blacklisting" ); + asset_update_operation uop; + uop.asset_to_update = noomo.id; + uop.issuer = dan_id; + uop.new_options = noomo.options; + // The blacklist is managed by Blacky + uop.new_options.blacklist_authorities.insert(blacky_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Upgrade Blacky so that he can manage the blacklist + upgrade_to_lifetime_member( blacky_id ); + + // Add Nathan to the blacklist, but do not add others + account_whitelist_operation wop; + wop.authorizing_account = blacky_id; + wop.account_to_list = nathan_id; + wop.new_listing = account_whitelist_operation::black_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + + // Cannot update order + GRAPHENE_REQUIRE_THROW( update_limit_order(order_id, {}, munee.amount(-1)), fc::exception ); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 48); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + + // Clear blacklist + wop.new_listing = account_whitelist_operation::no_listing; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + + // Can update order + update_limit_order(order_id, {}, munee.amount(-1)); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 47); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + } + + // Make a market whitelist + { + BOOST_TEST_MESSAGE( "Setting up market whitelisting" ); + asset_update_operation uop; + uop.asset_to_update = munee.id; + uop.issuer = dan_id; + uop.new_options = munee.options; + uop.new_options.whitelist_markets.insert( asset_id_type() ); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Cannot update order + GRAPHENE_REQUIRE_THROW( update_limit_order(order_id, {}, munee.amount(-1)), fc::exception ); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 47); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + + // Add Noomo to the whitelist + uop.new_options.whitelist_markets.insert( noomo.get_id() ); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Can update order + update_limit_order(order_id, {}, munee.amount(-1)); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 46); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + } + + // Make a market blacklist + { + BOOST_TEST_MESSAGE( "Setting up market blacklisting" ); + asset_update_operation uop; + uop.asset_to_update = munee.id; + uop.issuer = dan_id; + uop.new_options = munee.options; + uop.new_options.whitelist_markets.clear(); + uop.new_options.blacklist_markets.insert( noomo.get_id() ); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Cannot update order + GRAPHENE_REQUIRE_THROW( update_limit_order(order_id, {}, munee.amount(-1)), fc::exception ); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 46); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + + // Remove Noomo from the blacklist + uop.new_options.blacklist_markets.erase( noomo.get_id() ); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Can update order + update_limit_order(order_id, {}, munee.amount(-1)); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 45); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + } + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(limit_order_update_dust_test) +{ try { + + generate_blocks(HARDFORK_CORE_1604_TIME + 10); + set_expiration( db, trx ); + + ACTORS((nathan)(dan)); + + const auto& munee = create_user_issued_asset("MUNEE"); + + transfer(committee_account, nathan_id, asset(10000)); + issue_uia(dan, munee.amount(1000)); + + auto expiration = db.head_block_time() + 1000; + limit_order_id_type order_id = create_sell_order(nathan, asset(1000), munee.amount(100), expiration)->get_id(); + + REQUIRE_EXCEPTION_WITH_TEXT( update_limit_order(order_id, {}, asset(-995)), "order becomes too small" ); + + // Partially fill the first order so that we can test price changes + const limit_order_object* order2 = create_sell_order( dan, munee.amount(99), asset(990) ); + BOOST_CHECK( !order2 ); + + auto sell_price = asset(1000) / munee.amount(100); + BOOST_CHECK_EQUAL( fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price) ); + BOOST_CHECK_EQUAL( order_id(db).for_sale.value, 10 ); + + REQUIRE_EXCEPTION_WITH_TEXT( update_limit_order(order_id, price(asset(1000), munee.amount(99))), + "order becomes too small" ); + REQUIRE_EXCEPTION_WITH_TEXT( update_limit_order(order_id, price(asset(990), munee.amount(150)), asset(-5)), + "order becomes too small" ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(limit_order_update_match_test) +{ try { + + generate_blocks(HARDFORK_CORE_1604_TIME + 10); + set_expiration( db, trx ); + + ACTORS((nathan)); + + const auto& munee = create_user_issued_asset("MUNEE"); + + transfer(committee_account, nathan_id, asset(10000)); + issue_uia(nathan, munee.amount(1000)); + + auto expiration = db.head_block_time() + 1000; + limit_order_id_type order_id_1 = create_sell_order(nathan, asset(999), munee.amount(100), expiration)->get_id(); + limit_order_id_type order_id_2 = create_sell_order(nathan, munee.amount(100),asset(1001), expiration)->get_id(); + + update_limit_order(order_id_1, price(asset(1000), munee.amount(99)), asset(1)); + BOOST_REQUIRE( !db.find(order_id_1) ); + BOOST_REQUIRE_EQUAL(db.find(order_id_2)->amount_for_sale().amount.value, 1); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(limit_order_update_match_test_2) +{ try { + + generate_blocks(HARDFORK_CORE_1604_TIME + 10); + set_expiration( db, trx ); + + ACTORS((nathan)); + + const auto& munee = create_user_issued_asset("MUNEE"); + + transfer(committee_account, nathan_id, asset(10000)); + issue_uia(nathan, munee.amount(1000)); + + auto expiration = db.head_block_time() + 1000; + limit_order_id_type order_id_1 = create_sell_order(nathan, asset(999), munee.amount(100), expiration)->get_id(); + limit_order_id_type order_id_2 = create_sell_order(nathan, munee.amount(100),asset(1001), expiration)->get_id(); + + update_limit_order(order_id_2, price(munee.amount(100), asset(999))); + BOOST_REQUIRE( !db.find(order_id_1) ); + BOOST_REQUIRE( !db.find(order_id_2) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( call_order_update_test ) -{ - try { +{ try { ACTORS((dan)(sam)); const auto& bitusd = create_bitasset("USDBIT", sam.get_id()); @@ -174,15 +591,10 @@ BOOST_AUTO_TEST_CASE( call_order_update_test ) BOOST_TEST_MESSAGE( "attempting change call price to be below minimum for debt/collateral ratio" ); GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(0), asset(0)), fc::exception ); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( old_call_order_update_test_after_hardfork_583 ) -{ - try { +{ try { auto hf_time = HARDFORK_CORE_583_TIME; if( bsip77 ) @@ -281,15 +693,11 @@ BOOST_AUTO_TEST_CASE( old_call_order_update_test_after_hardfork_583 ) BOOST_TEST_MESSAGE( "attempting change call price to be below minimum for debt/collateral ratio" ); GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(0), asset(0)), fc::exception ); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( call_order_update_asset_auth_test ) -{ - try { +{ try { + generate_blocks( HARDFORK_CORE_973_TIME - fc::days(1) ); set_expiration( db, trx ); @@ -438,15 +846,11 @@ BOOST_AUTO_TEST_CASE( call_order_update_asset_auth_test ) generate_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( asset_settle_operation_asset_auth_test ) -{ - try { +{ try { + generate_blocks( HARDFORK_CORE_973_TIME - fc::days(1) ); set_expiration( db, trx ); @@ -604,15 +1008,11 @@ BOOST_AUTO_TEST_CASE( asset_settle_operation_asset_auth_test ) generate_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( bid_collateral_operation_asset_auth_test ) -{ - try { +{ try { + generate_blocks( HARDFORK_CORE_973_TIME - fc::days(1) ); set_expiration( db, trx ); @@ -683,7 +1083,7 @@ BOOST_AUTO_TEST_CASE( bid_collateral_operation_asset_auth_test ) BOOST_TEST_MESSAGE( "Trigger a black swan event" ); current_feed.settlement_price = bitusd.amount( 10 ) / backasset.amount( 100 ); publish_feed( bitusd, sam, current_feed ); - BOOST_REQUIRE( bitusd.bitasset_data(db).has_settlement() ); + BOOST_REQUIRE( bitusd.bitasset_data(db).is_globally_settled() ); // Reproduces bitshares-core issue #973: no asset authorization check thus Dan is able to bid collateral BOOST_TEST_MESSAGE( "Dan and Sam attempting to bid collateral" ); @@ -765,14 +1165,11 @@ BOOST_AUTO_TEST_CASE( bid_collateral_operation_asset_auth_test ) generate_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( asset_settle_cancel_operation_test_after_hf588 ) -{ +{ try { + set_expiration( db, trx ); BOOST_TEST_MESSAGE( "Creating a proposal containing a asset_settle_cancel_operation" ); @@ -825,15 +1222,15 @@ BOOST_AUTO_TEST_CASE( asset_settle_cancel_operation_test_after_hf588 ) return false; }); } -} + +} FC_LOG_AND_RETHROW() } /// Test case for bsip77: /// * the "initial_collateral_ratio" parameter can only be set after the BSIP77 hard fork /// * the parameter should be within a range // TODO removed the hard fork part after the hard fork, keep the valid range part BOOST_AUTO_TEST_CASE( bsip77_hardfork_time_and_param_valid_range_test ) -{ - try { +{ try { // Proceeds to a recent hard fork generate_blocks( HARDFORK_CORE_583_TIME ); @@ -1013,11 +1410,7 @@ BOOST_AUTO_TEST_CASE( bsip77_hardfork_time_and_param_valid_range_test ) generate_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( old_call_order_update_test_after_hardfork_bsip77_when_icr_not_set ) { @@ -1026,8 +1419,7 @@ BOOST_AUTO_TEST_CASE( old_call_order_update_test_after_hardfork_bsip77_when_icr_ } BOOST_AUTO_TEST_CASE( more_call_order_update_test ) -{ - try { +{ try { ACTORS((dan)(sam)(alice)(bob)); const auto& bitusd = create_bitasset("USDBIT", sam.get_id()); @@ -1121,16 +1513,10 @@ BOOST_AUTO_TEST_CASE( more_call_order_update_test ) BOOST_TEST_MESSAGE( "dan adding 1 core as collateral should not be allowed..." ); GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(0), core.amount(1) ), fc::exception ); - - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_583 ) -{ - try { +{ try { auto hf_time = HARDFORK_CORE_583_TIME; if( bsip77 ) @@ -1238,11 +1624,7 @@ BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_583 ) BOOST_TEST_MESSAGE( "dan borrow 2500 more usd wth 5003 more core should not be allowed..." ); GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), asset(5003) ), fc::exception ); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr_not_set ) { @@ -1251,8 +1633,7 @@ BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr } BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr_is_set ) -{ - try { +{ try { auto hf_time = HARDFORK_BSIP_77_TIME; generate_blocks( hf_time ); @@ -1445,15 +1826,10 @@ BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr generate_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr_is_fed ) -{ - try { +{ try { auto hf_time = HARDFORK_BSIP_77_TIME; generate_blocks( hf_time ); @@ -1628,14 +2004,11 @@ BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr generate_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( call_order_update_validation_test ) -{ +{ try { + call_order_update_operation op; // throw on default values @@ -1666,7 +2039,8 @@ BOOST_AUTO_TEST_CASE( call_order_update_validation_test ) op.extensions.value.target_collateral_ratio = 65535; op.validate(); // still valid -} + +} FC_LOG_AND_RETHROW() } /** * This test sets up a situation where a margin call will be executed and ensures that @@ -1684,6 +2058,7 @@ BOOST_AUTO_TEST_CASE( call_order_update_validation_test ) */ BOOST_AUTO_TEST_CASE( margin_call_limit_test ) { try { + ACTORS((buyer)(seller)(borrower)(borrower2)(feedproducer)); const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); @@ -1744,14 +2119,12 @@ BOOST_AUTO_TEST_CASE( margin_call_limit_test ) // this should trigger margin call without protection from the price feed. order = create_sell_order( borrower2, bitusd.amount(1000), core.amount(1800) ); BOOST_CHECK( order != nullptr ); - } catch( const fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( prediction_market ) { try { + ACTORS((judge)(dan)(nathan)); const auto& pmark = create_prediction_market("PMARK", judge_id); @@ -1802,14 +2175,12 @@ BOOST_AUTO_TEST_CASE( prediction_market ) generate_block(~database::skip_transaction_dupe_check); generate_blocks( db.get_dynamic_global_properties().next_maintenance_time ); generate_block(); - } catch( const fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( prediction_market_resolves_to_0 ) { try { + ACTORS((judge)(dan)(nathan)); const auto& pmark = create_prediction_market("PMARK", judge_id); @@ -1840,18 +2211,15 @@ BOOST_AUTO_TEST_CASE( prediction_market_resolves_to_0 ) generate_block(~database::skip_transaction_dupe_check); generate_blocks( db.get_dynamic_global_properties().next_maintenance_time ); generate_block(); -} catch( const fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } /*** * Prediction markets should not suffer a black swan (Issue #460) */ BOOST_AUTO_TEST_CASE( prediction_market_black_swan ) -{ - try { +{ try { + ACTORS((judge)(dan)(nathan)); // progress to recent hardfork @@ -1902,15 +2270,12 @@ BOOST_AUTO_TEST_CASE( prediction_market_black_swan ) generate_block(~database::skip_transaction_dupe_check); generate_blocks( db.get_dynamic_global_properties().next_maintenance_time ); generate_block(); - } catch( const fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( create_account_test ) -{ - try { +{ try { + generate_blocks( HARDFORK_CORE_143_TIME ); set_expiration( db, trx ); trx.operations.push_back(make_account()); @@ -1982,15 +2347,11 @@ BOOST_AUTO_TEST_CASE( create_account_test ) BOOST_CHECK_EQUAL( nathan_id(db).creation_block_num, db.head_block_num() ); BOOST_CHECK( nathan_id(db).creation_time == db.head_block_time() ); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( update_account ) -{ - try { +{ try { + const account_object& nathan = create_account("nathan", init_account_pub_key); const fc::ecc::private_key nathan_new_key = fc::ecc::private_key::generate(); const public_key_type key_id = nathan_new_key.get_public_key(); @@ -2032,15 +2393,12 @@ BOOST_AUTO_TEST_CASE( update_account ) } BOOST_CHECK( nathan.is_lifetime_member() ); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( transfer_core_asset ) -{ - try { +{ try { + INVOKE(create_account_test); account_id_type committee_account; @@ -2080,15 +2438,11 @@ BOOST_AUTO_TEST_CASE( transfer_core_asset ) BOOST_CHECK_EQUAL(get_balance(nathan_account, asset_id_type()(db)), 8000 - fee.amount.value); BOOST_CHECK_EQUAL(get_balance(account_id_type()(db), asset_id_type()(db)), committee_balance.amount.value + 2000); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( create_committee_member ) -{ - try { +{ try { + committee_member_create_operation op; op.committee_member_account = account_id_type(); op.fee = asset(); @@ -2103,29 +2457,23 @@ BOOST_AUTO_TEST_CASE( create_committee_member ) const committee_member_object& d = committee_member_id(db); BOOST_CHECK(d.committee_member_account == account_id_type()); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( create_mia ) -{ - try { +{ try { + const asset_object& bitusd = create_bitasset( "USDBIT" ); BOOST_CHECK(bitusd.symbol == "USDBIT"); BOOST_CHECK(bitusd.bitasset_data(db).options.short_backing_asset == asset_id_type()); BOOST_CHECK(bitusd.dynamic_asset_data_id(db).current_supply == 0); GRAPHENE_REQUIRE_THROW( create_bitasset("USDBIT"), fc::exception); - } catch ( const fc::exception& e ) { - elog( "${e}", ("e", e.to_detail_string() ) ); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( update_mia ) -{ - try { +{ try { + INVOKE(create_mia); generate_block(); const asset_object& bit_usd = get_asset("USDBIT"); @@ -2172,16 +2520,12 @@ BOOST_AUTO_TEST_CASE( update_mia ) trx.operations.back() = op; PUSH_TX( db, trx, ~0 ); BOOST_CHECK(bit_usd.issuer == account_id_type()); - } catch ( const fc::exception& e ) { - elog( "${e}", ("e", e.to_detail_string() ) ); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( create_uia ) -{ - try { +{ try { + asset_id_type test_asset_id { db.get_index().get_next_id() }; asset_create_operation creator; creator.issuer = account_id_type(); @@ -2230,16 +2574,11 @@ BOOST_AUTO_TEST_CASE( create_uia ) BOOST_CHECK_EQUAL( test_asset_id(db).creation_block_num, db.head_block_num() ); BOOST_CHECK( test_asset_id(db).creation_time == db.head_block_time() ); - } catch(fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( update_uia ) -{ - using namespace graphene; - try { +{ try { + INVOKE(create_uia); const auto& test = get_asset(UIA_TEST_SYMBOL); const auto& nathan = create_account("nathan"); @@ -2299,7 +2638,7 @@ BOOST_AUTO_TEST_CASE( update_uia ) issue_op.issue_to_account = nathan.get_id(); trx.operations.push_back(issue_op); PUSH_TX(db, trx, ~0); - + BOOST_TEST_MESSAGE( "Make sure white_list can't be re-enabled (after tokens issued)" ); op.new_options.issuer_permissions = test.options.issuer_permissions; op.new_options.flags = test.options.flags; @@ -2313,18 +2652,11 @@ BOOST_AUTO_TEST_CASE( update_uia ) op.issuer = account_id_type(); GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0 ), fc::exception); op.new_issuer.reset(); - } catch(fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( update_uia_issuer ) -{ - using namespace graphene; - using namespace graphene::chain; - using namespace graphene::chain::test; - try { +{ try { // Lambda for creating accounts with 2 different keys auto create_account_2_keys = [&]( const string name, @@ -2440,17 +2772,11 @@ BOOST_AUTO_TEST_CASE( update_uia_issuer ) BOOST_CHECK(test_id(db).issuer == bob_id); - } - catch( const fc::exception& e ) - { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( issue_uia ) -{ - try { +{ try { + INVOKE(create_uia); INVOKE(create_account_test); @@ -2482,15 +2808,12 @@ BOOST_AUTO_TEST_CASE( issue_uia ) BOOST_CHECK(test_dynamic_data.current_supply == 10000000); BOOST_CHECK(test_dynamic_data.accumulated_fees == 0); BOOST_CHECK(test_dynamic_data.fee_pool == 0); - } catch(fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( transfer_uia ) -{ - try { +{ try { + INVOKE(issue_uia); const asset_object& uia = *db.get_index_type().indices().get().find(UIA_TEST_SYMBOL); @@ -2511,15 +2834,12 @@ BOOST_AUTO_TEST_CASE( transfer_uia ) PUSH_TX( db, trx, ~0 ); BOOST_CHECK_EQUAL(get_balance(nathan, uia), 10000000 - 10000); BOOST_CHECK_EQUAL(get_balance(committee, uia), 10000); - } catch(fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( create_buy_uia_multiple_match_new ) { try { + INVOKE( issue_uia ); const asset_object& core_asset = get_asset( UIA_TEST_SYMBOL ); const asset_object& test_asset = get_asset( GRAPHENE_SYMBOL ); @@ -2550,16 +2870,12 @@ BOOST_AUTO_TEST_CASE( create_buy_uia_multiple_match_new ) BOOST_CHECK_EQUAL( get_balance( seller_account, test_asset ), 200 ); BOOST_CHECK_EQUAL( get_balance( buyer_account, core_asset ), 297 ); BOOST_CHECK_EQUAL( core_asset.dynamic_asset_data_id(db).accumulated_fees.value , 3 ); - } - catch ( const fc::exception& e ) - { - elog( "${e}", ("e", e.to_detail_string() ) ); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( create_buy_exact_match_uia ) { try { + INVOKE( issue_uia ); const asset_object& test_asset = get_asset( UIA_TEST_SYMBOL ); const asset_object& core_asset = get_asset( GRAPHENE_SYMBOL ); @@ -2590,17 +2906,13 @@ BOOST_AUTO_TEST_CASE( create_buy_exact_match_uia ) BOOST_CHECK_EQUAL( get_balance( seller_account, test_asset ), 99 ); BOOST_CHECK_EQUAL( get_balance( buyer_account, core_asset ), 100 ); BOOST_CHECK_EQUAL( test_asset.dynamic_asset_data_id(db).accumulated_fees.value , 1 ); - } - catch ( const fc::exception& e ) - { - elog( "${e}", ("e", e.to_detail_string() ) ); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( create_buy_uia_multiple_match_new_reverse ) { try { + INVOKE( issue_uia ); const asset_object& test_asset = get_asset( UIA_TEST_SYMBOL ); const asset_object& core_asset = get_asset( GRAPHENE_SYMBOL ); @@ -2631,16 +2943,12 @@ BOOST_AUTO_TEST_CASE( create_buy_uia_multiple_match_new_reverse ) BOOST_CHECK_EQUAL( get_balance( seller_account, test_asset ), 198 ); BOOST_CHECK_EQUAL( get_balance( buyer_account, core_asset ), 300 ); BOOST_CHECK_EQUAL( test_asset.dynamic_asset_data_id(db).accumulated_fees.value , 2 ); - } - catch ( const fc::exception& e ) - { - elog( "${e}", ("e", e.to_detail_string() ) ); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( create_buy_uia_multiple_match_new_reverse_fract ) { try { + INVOKE( issue_uia ); const asset_object& test_asset = get_asset( UIA_TEST_SYMBOL ); const asset_object& core_asset = get_asset( GRAPHENE_SYMBOL ); @@ -2674,18 +2982,12 @@ BOOST_AUTO_TEST_CASE( create_buy_uia_multiple_match_new_reverse_fract ) BOOST_CHECK_EQUAL( get_balance( buyer_account, core_asset ), 30 ); BOOST_CHECK_EQUAL( get_balance( seller_account, core_asset ), 0 ); BOOST_CHECK_EQUAL( test_asset.dynamic_asset_data_id(db).accumulated_fees.value , 2 ); - } - catch ( const fc::exception& e ) - { - elog( "${e}", ("e", e.to_detail_string() ) ); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( uia_fees ) -{ - try { +{ try { + INVOKE( issue_uia ); enable_fees(); @@ -2744,14 +3046,12 @@ BOOST_AUTO_TEST_CASE( uia_fees ) BOOST_CHECK_EQUAL(get_balance(committee_account, test_asset), 200); BOOST_CHECK(asset_dynamic.accumulated_fees == fee.amount.value * 3); BOOST_CHECK(asset_dynamic.fee_pool == 1000*prec - core_fee.amount.value * 3); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( cancel_limit_order_test ) { try { + INVOKE( issue_uia ); const asset_object& test_asset = get_asset( UIA_TEST_SYMBOL ); const account_object& buyer_account = create_account( "buyer" ); @@ -2764,18 +3064,12 @@ BOOST_AUTO_TEST_CASE( cancel_limit_order_test ) auto refunded = cancel_limit_order( *sell_order ); BOOST_CHECK( refunded == asset(1000) ); BOOST_CHECK_EQUAL( get_balance(buyer_account, asset_id_type()(db)), 10000 ); - } - catch ( const fc::exception& e ) - { - elog( "${e}", ("e", e.to_detail_string() ) ); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( witness_feeds ) -{ - using namespace graphene::chain; - try { +{ try { + INVOKE( create_mia ); { auto& current = get_asset( "USDBIT" ); @@ -2825,11 +3119,8 @@ BOOST_AUTO_TEST_CASE( witness_feeds ) BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 30.0 / GRAPHENE_BLOCKCHAIN_PRECISION); BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO); - } catch (const fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } /** * Create an order that cannot be filled immediately and have the @@ -2837,6 +3128,7 @@ BOOST_AUTO_TEST_CASE( witness_feeds ) */ BOOST_AUTO_TEST_CASE( limit_order_fill_or_kill ) { try { + INVOKE(issue_uia); const account_object& nathan = get_account("nathan"); const asset_object& test = get_asset(UIA_TEST_SYMBOL); @@ -2854,6 +3146,7 @@ BOOST_AUTO_TEST_CASE( limit_order_fill_or_kill ) op.fill_or_kill = false; trx.operations.back() = op; PUSH_TX( db, trx, ~0 ); + } FC_LOG_AND_RETHROW() } /// Shameless code coverage plugging. Otherwise, these calls never happen. @@ -2927,7 +3220,7 @@ BOOST_AUTO_TEST_CASE( witness_pay_test ) PUSH_TX( db, trx ); auto pay_fee_time = db.head_block_time().sec_since_epoch(); trx.clear(); - BOOST_CHECK( get_balance(*nathan, *core) == 20000*prec - account_upgrade_operation::fee_parameters_type().membership_lifetime_fee );; + BOOST_CHECK( get_balance(*nathan, *core) == 20000*prec - account_upgrade_operation::fee_params_t().membership_lifetime_fee );; generate_block(); nathan = &get_account("nathan"); @@ -2986,9 +3279,8 @@ BOOST_AUTO_TEST_CASE( witness_pay_test ) * can be burned, and all supplies add up. */ BOOST_AUTO_TEST_CASE( reserve_asset_test ) -{ - try - { +{ try { + ACTORS((alice)(bob)(sam)(judge)); const auto& basset = create_bitasset("USDBIT", judge_id); const auto& uasset = create_user_issued_asset(UIA_TEST_SYMBOL); @@ -3059,25 +3351,19 @@ BOOST_AUTO_TEST_CASE( reserve_asset_test ) BOOST_CHECK_EQUAL( get_balance( alice, uasset ), init_balance - reserve_amount ); BOOST_CHECK_EQUAL( (uasset.reserved( db ) - initial_reserve).value, reserve_amount ); verify_asset_supplies(db); - } - catch (fc::exception& e) - { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( call_order_update_evaluator_test ) -{ - try - { +{ try { + ACTORS( (alice) (bob) ); transfer(committee_account, alice_id, asset(10000000 * GRAPHENE_BLOCKCHAIN_PRECISION)); const auto& core = asset_id_type()(db); // attempt to increase current supply beyond max_supply - const auto& bitjmj = create_bitasset( "JMJBIT", alice_id, 100, charge_market_fee, 2U, + const auto& bitjmj = create_bitasset( "JMJBIT", alice_id, 100, charge_market_fee, 2U, asset_id_type{}, GRAPHENE_MAX_SHARE_SUPPLY / 2 ); auto bitjmj_id = bitjmj.get_id(); share_type original_max_supply = bitjmj.options.max_supply; @@ -3112,7 +3398,7 @@ BOOST_AUTO_TEST_CASE( call_order_update_evaluator_test ) BOOST_REQUIRE_GT(newbitjmj.options.max_supply.value, original_max_supply.value); // now try with an asset after the hardfork - const auto& bitusd = create_bitasset( "USDBIT", alice_id, 100, charge_market_fee, 2U, + const auto& bitusd = create_bitasset( "USDBIT", alice_id, 100, charge_market_fee, 2U, asset_id_type{}, GRAPHENE_MAX_SHARE_SUPPLY / 2 ); { @@ -3171,17 +3457,16 @@ BOOST_AUTO_TEST_CASE( call_order_update_evaluator_test ) set_expiration( db, tx ); PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures ); } - } FC_LOG_AND_RETHROW() -} + +} FC_LOG_AND_RETHROW() } /** * This test demonstrates how using the call_order_update_operation to * trigger a margin call is legal if there is a matching order. */ BOOST_AUTO_TEST_CASE( cover_with_collateral_test ) -{ - try - { +{ try { + ACTORS((alice)(bob)(sam)); const auto& bitusd = create_bitasset("USDBIT", sam_id); const auto& core = asset_id_type()(db); @@ -3249,13 +3534,8 @@ BOOST_AUTO_TEST_CASE( cover_with_collateral_test ) BOOST_CHECK( db.find( order1_id ) == nullptr ); BOOST_CHECK( db.find( order2_id ) == nullptr ); - } - catch (fc::exception& e) - { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( vesting_balance_create_test ) { try { diff --git a/tests/tests/oso_take_profit_order_tests.cpp b/tests/tests/oso_take_profit_order_tests.cpp new file mode 100644 index 0000000000..b8e4877413 --- /dev/null +++ b/tests/tests/oso_take_profit_order_tests.cpp @@ -0,0 +1,1691 @@ +/* + * Copyright (c) 2023 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "../common/database_fixture.hpp" + +#include +#include +#include + +#include + +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( oso_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( oso_take_profit_order_hardfork_time_test ) +{ try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_CORE_2362_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + + const asset_object& usd = create_user_issued_asset( "MYUSD" ); + asset_id_type usd_id = usd.get_id(); + + // Before the hard fork, unable to create a limit order with the "on_fill" extension + // or create with proposals, + // but can create without on_fill + create_take_profit_order_action tpa1 { asset_id_type(), 5, GRAPHENE_100_PERCENT, 3600, false }; + vector on_fill { tpa1 }; + + // With on_fill + BOOST_CHECK_THROW( create_sell_order( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), price::unit_price(), on_fill ), + fc::exception ); + // Without on_fill + create_sell_order( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), price::unit_price(), {} ); + + // Proposal with on_fill + limit_order_create_operation cop1 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), on_fill ); + BOOST_CHECK_THROW( propose( cop1 ), fc::exception ); + // Proposal without on_fill + limit_order_create_operation cop2 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), {} ); + propose( cop2 ); + +} FC_LOG_AND_RETHROW() } + +/// Tests setting up oso with limit_order_create_operation +BOOST_AUTO_TEST_CASE( oso_take_profit_order_setup_test ) +{ try { + + // Proceeds to the hard fork + generate_blocks( HARDFORK_CORE_2535_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + + const asset_object& usd = create_user_issued_asset( "MYUSD" ); + asset_id_type usd_id = usd.get_id(); + + // Spread percentage should be positive + create_take_profit_order_action tpa1 { asset_id_type(), 0, GRAPHENE_100_PERCENT, 3600, false }; + vector on_fill { tpa1 }; + BOOST_CHECK_THROW( create_sell_order( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), price::unit_price(), on_fill ), + fc::exception ); + // Cannot propose either + limit_order_create_operation cop1 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), on_fill ); + BOOST_CHECK_THROW( propose( cop1 ), fc::exception ); + + // Size percentage should be positive + tpa1 = { asset_id_type(), 1, 0, 3600, false }; + on_fill = { tpa1 }; + BOOST_CHECK_THROW( create_sell_order( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), price::unit_price(), on_fill ), + fc::exception ); + // Cannot propose either + cop1 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), on_fill ); + BOOST_CHECK_THROW( propose( cop1 ), fc::exception ); + + // Size percentage should not exceed 100% + tpa1 = { asset_id_type(), 1, GRAPHENE_100_PERCENT + 1, 3600, false }; + on_fill = { tpa1 }; + BOOST_CHECK_THROW( create_sell_order( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), price::unit_price(), on_fill ), + fc::exception ); + // Cannot propose either + cop1 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), on_fill ); + BOOST_CHECK_THROW( propose( cop1 ), fc::exception ); + + // Expiration should be positive + tpa1 = { asset_id_type(), 1, GRAPHENE_100_PERCENT, 0, false }; + on_fill = { tpa1 }; + BOOST_CHECK_THROW( create_sell_order( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), price::unit_price(), on_fill ), + fc::exception ); + // Cannot propose either + cop1 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), on_fill ); + BOOST_CHECK_THROW( propose( cop1 ), fc::exception ); + + // Fee asset should exist + tpa1 = { usd_id + 1, 1, GRAPHENE_100_PERCENT, 3600, false }; + on_fill = { tpa1 }; + BOOST_CHECK_THROW( create_sell_order( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), price::unit_price(), on_fill ), + fc::exception ); + // Can propose + cop1 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), on_fill ); + propose( cop1 ); + + // on_fill must contain only 1 action + tpa1 = { asset_id_type(), 1, GRAPHENE_100_PERCENT, 3600, false }; + // size == 0 + on_fill = {}; + BOOST_CHECK_THROW( create_sell_order( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), price::unit_price(), on_fill ), + fc::exception ); + // Can propose + cop1 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), on_fill ); + propose( cop1 ); + // size > 1 + on_fill = { tpa1, tpa1 }; + BOOST_CHECK_THROW( create_sell_order( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), price::unit_price(), on_fill ), + fc::exception ); + // Can propose + cop1 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), on_fill ); + propose( cop1 ); + + // A valid operation with on_fill + tpa1 = { asset_id_type(), 1, GRAPHENE_100_PERCENT, 3600, false }; + on_fill = { tpa1 }; + const limit_order_object* order1 = create_sell_order( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), price::unit_price(), on_fill ); + // Can propose + cop1 = make_limit_order_create_op( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), on_fill ); + propose( cop1 ); + + BOOST_REQUIRE( order1 ); + limit_order_id_type order1_id = order1->get_id(); + + // Another order without on_fill + const limit_order_object* order2 = create_sell_order( sam_id, asset(1), asset(1, usd_id), + time_point_sec::maximum(), price::unit_price(), {} ); + BOOST_REQUIRE( order2 ); + limit_order_id_type order2_id = order2->get_id(); + + // Final check + const auto& check_result = [&]() + { + BOOST_CHECK( order2_id(db).on_fill.empty() ); + + BOOST_REQUIRE_EQUAL( order1_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( order1_id(db).on_fill.front().is_type() ); + const auto& action = order1_id(db).on_fill.front().get(); + BOOST_CHECK( action.fee_asset_id == tpa1.fee_asset_id ); + BOOST_CHECK( action.spread_percent == tpa1.spread_percent ); + BOOST_CHECK( action.size_percent == tpa1.size_percent ); + BOOST_CHECK( action.expiration_seconds == tpa1.expiration_seconds ); + BOOST_CHECK( action.repeat == tpa1.repeat ); + }; + + check_result(); + + generate_block(); + + check_result(); + +} FC_LOG_AND_RETHROW() } + +/// Tests order-sends-take-profit-order and related order cancellation +BOOST_AUTO_TEST_CASE( oso_take_profit_order_trigger_and_cancel_test ) +{ try { + + // Proceeds to the hard fork + generate_blocks( HARDFORK_CORE_2535_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)(ted)); + + additional_asset_options_t usd_options; + usd_options.value.taker_fee_percent = 80; // 0.8% taker fee + + const asset_object& usd = create_user_issued_asset( "MYUSD", ted, charge_market_fee | white_list, + price(asset(1, asset_id_type(1)), asset(1)), + 4, 30, usd_options ); // 0.3% maker fee + asset_id_type usd_id = usd.get_id(); + asset_id_type core_id; + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( ted, asset(init_amount) ); + issue_uia( ted, asset(init_amount, usd_id) ); + + int64_t expected_balance_sam_core = init_amount; + int64_t expected_balance_ted_core = init_amount; + int64_t expected_balance_sam_usd = 0; + int64_t expected_balance_ted_usd = init_amount; + + const auto& check_balances = [&]() { + BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, expected_balance_sam_core ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, core_id ).amount.value, expected_balance_ted_core ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, expected_balance_sam_usd ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, usd_id ).amount.value, expected_balance_ted_usd ); + }; + + check_balances(); + + // Sam sells CORE for USD with on_fill + // fee_asset, spread, size, expiration, repeat + create_take_profit_order_action tpa1 { core_id, 100, 10000, 3600, false }; + vector on_fill_1 { tpa1 }; + + const limit_order_object* sell_order1 = create_sell_order( sam_id, asset(10000), asset(12345, usd_id), + time_point_sec::maximum(), price::unit_price(), on_fill_1 ); + BOOST_REQUIRE( sell_order1 ); + limit_order_id_type sell_order1_id = sell_order1->get_id(); + + limit_order_id_type last_order_id = sell_order1_id; + + BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); + + BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type() ); + const auto& action_s1 = sell_order1_id(db).on_fill.front().get(); + BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id ); + BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent ); + BOOST_CHECK( action_s1.size_percent == tpa1.size_percent ); + BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds ); + BOOST_CHECK( action_s1.repeat == tpa1.repeat ); + + expected_balance_sam_core -= 10000; + check_balances(); + + // Ted buys CORE with USD without on_fill, partially fills Sam's order + const limit_order_object* buy_order1 = create_sell_order( ted_id, asset(1235, usd_id), asset(1000) ); + last_order_id = last_order_id + 1; + + // The buy order is smaller, it gets fully filled + BOOST_CHECK( !buy_order1 ); + expected_balance_ted_core += 1000; + expected_balance_ted_usd -= 1235; + + // The newly created take profit order is a buy order + last_order_id = last_order_id + 1; + limit_order_id_type buy_order2_id = last_order_id; + + auto buy_order2_expiration = db.head_block_time() + 3600; + + const auto& check_result_1 = [&]() + { + // The sell order is partially filled + BOOST_REQUIRE( db.find(sell_order1_id) ); + // The take profit order + BOOST_REQUIRE( db.find(buy_order2_id) ); + BOOST_CHECK( buy_order2_id(db).seller == sam_id ); + // The sell order gets 1235, market fee = round_down(1235 * 30 / 10000) = 3 + BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 ); // 1235 - 3 + // price = (12345 / 10000) / 101% = 12345 / 10100 + // min to receive = round_up( 1232 * 10100 / 12345 ) = 1008 + // updated price = 1232 / 1008 + BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) ); + BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration ); + BOOST_CHECK( buy_order2_id(db).take_profit_order_id == sell_order1_id ); + BOOST_CHECK( buy_order2_id(db).on_fill.empty() ); + + // The sell order is partially filled, pays 1000 + BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 ); // 10000 - 1000 + BOOST_CHECK( sell_order1_id(db).take_profit_order_id == buy_order2_id ); + + check_balances(); + }; + + check_result_1(); + + generate_block(); + + check_result_1(); + + // Sam sells more CORE for USD with on_fill + // fee_asset, spread, size, expiration, repeat + create_take_profit_order_action tpa2 { usd_id, 70, 9700, uint32_t(-1), true }; + vector on_fill_2 { tpa2 }; + + const limit_order_object* sell_order2 = create_sell_order( sam_id, asset(10000), asset(13000, usd_id), + time_point_sec::maximum(), price::unit_price(), on_fill_2 ); + last_order_id = last_order_id + 1; + + BOOST_REQUIRE( sell_order2 ); + limit_order_id_type sell_order2_id = sell_order2->get_id(); + + BOOST_REQUIRE_EQUAL( sell_order2_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order2_id(db).on_fill.front().is_type() ); + const auto& action_s2 = sell_order2_id(db).on_fill.front().get(); + BOOST_CHECK( action_s2.fee_asset_id == tpa2.fee_asset_id ); + BOOST_CHECK( action_s2.spread_percent == tpa2.spread_percent ); + BOOST_CHECK( action_s2.size_percent == tpa2.size_percent ); + BOOST_CHECK( action_s2.expiration_seconds == tpa2.expiration_seconds ); + BOOST_CHECK( action_s2.repeat == tpa2.repeat ); + + expected_balance_sam_core -= 10000; + check_balances(); + + // Sam sells yet more CORE for USD with on_fill + // fee_asset, spread, size, expiration, repeat + create_take_profit_order_action tpa3 { usd_id, 70, 9970, 3600, true }; + vector on_fill_3 { tpa3 }; + + const limit_order_object* sell_order3 = create_sell_order( sam_id, asset(10000), asset(34000, usd_id), + db.head_block_time() + 7200, price::unit_price(), on_fill_3 ); + last_order_id = last_order_id + 1; + + BOOST_REQUIRE( sell_order3 ); + limit_order_id_type sell_order3_id = sell_order3->get_id(); + + // Data backup + auto sell_order3_expiration = sell_order3->expiration; + + BOOST_REQUIRE_EQUAL( sell_order3_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order3_id(db).on_fill.front().is_type() ); + const auto& action_s3 = sell_order3_id(db).on_fill.front().get(); + BOOST_CHECK( action_s3.fee_asset_id == tpa3.fee_asset_id ); + BOOST_CHECK( action_s3.spread_percent == tpa3.spread_percent ); + BOOST_CHECK( action_s3.size_percent == tpa3.size_percent ); + BOOST_CHECK( action_s3.expiration_seconds == tpa3.expiration_seconds ); + BOOST_CHECK( action_s3.repeat == tpa3.repeat ); + + expected_balance_sam_core -= 10000; + check_balances(); + + // Ted buys CORE with USD with on_fill, fills Sam's orders + // fee_asset, spread, size, expiration, repeat + create_take_profit_order_action tpa4 { core_id, 1, 9999, uint32_t(-1), true }; + vector on_fill_4 { tpa4 }; + + const limit_order_object* buy_order3 = create_sell_order( ted_id, asset(30000, usd_id), asset(7000), + time_point_sec::maximum(), price::unit_price(), on_fill_4 ); + last_order_id = last_order_id + 1; + + // buy_order3 is fully filled + BOOST_CHECK( !buy_order3 ); + + // The take profit order created by sell_order1 is updated + auto buy_order2_expiration_new = db.head_block_time() + 3600; + + // The take profit order created by buy_order3 is a sell order + last_order_id = last_order_id + 1; + limit_order_id_type sell_order4_id = last_order_id; + auto sell_order4_expiration = time_point_sec::maximum(); + + // The take profit order created by sell_order2 is a buy order + last_order_id = last_order_id + 1; + limit_order_id_type buy_order4_id = last_order_id; + auto buy_order4_expiration = time_point_sec::maximum(); + + // The take profit order created by sell_order3 is a buy order + last_order_id = last_order_id + 1; + limit_order_id_type buy_order5_id = last_order_id; + auto buy_order5_expiration = db.head_block_time() + 3600; + + expected_balance_ted_core += 1; // see calculation below + expected_balance_ted_usd -= (30000 - 1); // buy_order3 refund 1, see calculation below + expected_balance_sam_usd += (388 + 17); // sell_order2 and sell_order3, see calculation below + + + const auto& check_result_2 = [&]() + { + // sell_order1 gets fully filled + BOOST_CHECK( !db.find(sell_order1_id) ); + + // The take profit order linked to sell_order1 (buy_order2) is updated + BOOST_REQUIRE( db.find(buy_order2_id) ); + BOOST_CHECK( buy_order2_id(db).seller == sam_id ); + // sell_order1 pays 9000, gets round_down(9000 * 12345 / 10000) = 11110, market fee = 11110 * 30 / 10000 = 33 + BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 12309 ); // 1232 + 11110 - 33 + // price = (12345 / 10000) / 101% = 12345 / 10100 + // min to receive = round_up( 12309 * 10100 / 12345 ) = 10071 + // updated price = 12309 / 10071 + BOOST_CHECK( buy_order2_id(db).sell_price == asset(12309,usd_id) / asset(10071) ); + BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration_new ); + BOOST_CHECK( buy_order2_id(db).expiration != buy_order2_expiration ); + BOOST_CHECK( !buy_order2_id(db).take_profit_order_id ); // cleared + BOOST_CHECK( buy_order2_id(db).on_fill.empty() ); + + // buy_order3 pays 11110, gets 9000, remaining for sale = 30000 - 11110 = 18890 + + // sell_order2 gets fully filled + BOOST_CHECK( !db.find(sell_order2_id) ); + + // The take profit order created by sell_order2 + BOOST_REQUIRE( db.find(buy_order4_id) ); + BOOST_CHECK( buy_order4_id(db).seller == sam_id ); + // sell_order2 gets 13000, market fee = round_down(13000 * 30 / 10000) = 39 + // gets = 13000 - 39 = 12961 + // take profit order size = round_up(12961 * 9700 / 10000) = 12573 + // Sam USD balance change = 12961 - 12573 = 388 + BOOST_CHECK_EQUAL( buy_order4_id(db).for_sale.value, 12573 ); + // price = (13000 / 10000) / 100.7% = 13000 / 10070 + // min to receive = round_up( 12573 * 10070 / 13000 ) = 9740 + // updated price = 12573 / 9740 + BOOST_CHECK( buy_order4_id(db).sell_price == asset(12573,usd_id) / asset(9740) ); + BOOST_CHECK( buy_order4_id(db).expiration == buy_order4_expiration ); + BOOST_CHECK( !buy_order4_id(db).take_profit_order_id ); + + BOOST_REQUIRE_EQUAL( buy_order4_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( buy_order4_id(db).on_fill.front().is_type() ); + const auto& action_b4 = buy_order4_id(db).on_fill.front().get(); + BOOST_CHECK( action_b4.fee_asset_id == tpa2.fee_asset_id ); + BOOST_CHECK( action_b4.spread_percent == tpa2.spread_percent ); + BOOST_CHECK( action_b4.size_percent == tpa2.size_percent ); + BOOST_CHECK( action_b4.expiration_seconds == tpa2.expiration_seconds ); + BOOST_CHECK( action_b4.repeat == tpa2.repeat ); + + // buy_order3 pays 13000, gets 10000, remaining for sale = 18890 - 13000 = 5890 + + // sell_order3 gets partially filled + BOOST_REQUIRE( db.find(sell_order3_id) ); + // The take profit order created by sell_order3 + BOOST_REQUIRE( db.find(buy_order5_id) ); + BOOST_CHECK( buy_order5_id(db).seller == sam_id ); + // sell_order3 gets 5890, pays round_down(5890 * 10000 / 34000) = 1732 + // updated gets = round_up(1732 * 34000 / 10000) = 5889, refund = 5890 - 5889 = 1 + // market fee = round_down(5889 * 30 / 10000) = 17 + // gets = 5889 - 17 = 5872 + // take profit order size = round_up(5872 * 9970 / 10000) = 5855 + // Sam USD balance change = 5872 - 5855 = 17 + BOOST_CHECK_EQUAL( buy_order5_id(db).for_sale.value, 5855 ); + // price = (34000 / 10000) / 100.7% = 34000 / 10070 + // min to receive = round_up( 5855 * 10070 / 34000 ) = 1735 + // updated price = 5855 / 1735 = 3.374639769 + BOOST_CHECK( buy_order5_id(db).sell_price == asset(5855,usd_id) / asset(1735) ); + BOOST_CHECK( buy_order5_id(db).expiration == buy_order5_expiration ); + BOOST_CHECK( buy_order5_id(db).take_profit_order_id == sell_order3_id ); + + BOOST_REQUIRE_EQUAL( buy_order5_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( buy_order5_id(db).on_fill.front().is_type() ); + const auto& action_b5 = buy_order5_id(db).on_fill.front().get(); + BOOST_CHECK( action_b5.fee_asset_id == tpa3.fee_asset_id ); + BOOST_CHECK( action_b5.spread_percent == tpa3.spread_percent ); + BOOST_CHECK( action_b5.size_percent == tpa3.size_percent ); + BOOST_CHECK( action_b5.expiration_seconds == tpa3.expiration_seconds ); + BOOST_CHECK( action_b5.repeat == tpa3.repeat ); + + // sell_order3 gets partially filled, pays 1732 + BOOST_CHECK_EQUAL( sell_order3_id(db).for_sale.value, 8268 ); // 10000 - 1732 + BOOST_CHECK( sell_order3_id(db).take_profit_order_id == buy_order5_id ); + + // buy_order3 gets 1732, pays 5889, refund 1 + + // The take profit order created by buy_order3 + BOOST_REQUIRE( db.find(sell_order4_id) ); + BOOST_CHECK( sell_order4_id(db).seller == ted_id ); + // buy_order3 got in total 9000 + 10000 + 1732 = 20732, market fee = 0 + // take profit order size = + // round_up(9000 * 9999 / 10000) + round_up(10000 * 9999 / 10000) + round_up(1732 * 9999 / 10000) = 20731 + // Ted CORE balance change = 20732 - 20731 = 1 + BOOST_CHECK_EQUAL( sell_order4_id(db).for_sale.value, 20731 ); + // price = (7000 / 30000) / 100.01% = 7000 / 30003 + // min to receive = round_up( 20731 * 30003 / 7000 ) = 88857 + // updated price = 20731 / 88857 + BOOST_CHECK( sell_order4_id(db).sell_price == asset(20731) / asset(88857,usd_id) ); + BOOST_CHECK( sell_order4_id(db).expiration == sell_order4_expiration ); + BOOST_CHECK( !sell_order4_id(db).take_profit_order_id ); + + BOOST_REQUIRE_EQUAL( sell_order4_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order4_id(db).on_fill.front().is_type() ); + const auto& action_s4 = sell_order4_id(db).on_fill.front().get(); + BOOST_CHECK( action_s4.fee_asset_id == tpa4.fee_asset_id ); + BOOST_CHECK( action_s4.spread_percent == tpa4.spread_percent ); + BOOST_CHECK( action_s4.size_percent == tpa4.size_percent ); + BOOST_CHECK( action_s4.expiration_seconds == tpa4.expiration_seconds ); + BOOST_CHECK( action_s4.repeat == tpa4.repeat ); + + check_balances(); + }; + + check_result_2(); + + generate_block(); + + check_result_2(); + + // Ted sells CORE for USD with on_fill + // fee_asset, spread, size, expiration, repeat + create_take_profit_order_action tpa5 { usd_id, 65535, 1, 8800, true }; + vector on_fill_5 { tpa5 }; + + const limit_order_object* sell_order5 = create_sell_order( ted_id, asset(1), asset(1, usd_id), + db.head_block_time() + 9900, price::unit_price(), on_fill_5 ); + last_order_id = last_order_id + 1; + + // sell_order5 is fully filled + BOOST_CHECK( !sell_order5 ); + + // buy_order5 is partially filled + // The take profit order linked to buy_order5 (sell_order3) is updated + auto sell_order3_expiration_new = db.head_block_time() + 3600; + + // The take profit order created by sell_order5 is a buy order + last_order_id = last_order_id + 1; + limit_order_id_type buy_order6_id = last_order_id; + auto buy_order6_expiration = db.head_block_time() + 8800; + + expected_balance_ted_core -= 1; // see calculation below + expected_balance_ted_usd += 2; // see calculation below + + const auto check_result_3 = [&]() + { + // buy_order5 is partially filled + BOOST_REQUIRE( db.find(buy_order5_id) ); + BOOST_CHECK( buy_order5_id(db).seller == sam_id ); + // buy_order5 gets 1, pays round_down(1 * 5855 / 1735) = 3 + BOOST_CHECK_EQUAL( buy_order5_id(db).for_sale.value, 5852 ); // 5855 - 3 + BOOST_CHECK( buy_order5_id(db).sell_price == asset(5855,usd_id) / asset(1735) ); // unchanged + BOOST_CHECK( buy_order5_id(db).expiration == buy_order5_expiration ); // unchanged + BOOST_CHECK( buy_order5_id(db).take_profit_order_id == sell_order3_id ); // unchanged + + // All unchanged + BOOST_REQUIRE_EQUAL( buy_order5_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( buy_order5_id(db).on_fill.front().is_type() ); + const auto& action_b5 = buy_order5_id(db).on_fill.front().get(); + BOOST_CHECK( action_b5.fee_asset_id == tpa3.fee_asset_id ); + BOOST_CHECK( action_b5.spread_percent == tpa3.spread_percent ); + BOOST_CHECK( action_b5.size_percent == tpa3.size_percent ); + BOOST_CHECK( action_b5.expiration_seconds == tpa3.expiration_seconds ); + BOOST_CHECK( action_b5.repeat == tpa3.repeat ); + + // The take profit order linked to buy_order5 (sell_order3) is updated + BOOST_REQUIRE( db.find(sell_order3_id) ); + BOOST_CHECK( sell_order3_id(db).seller == sam_id ); + // new amount for sale = round_up(1 * 99.7%) = 1, account balances unchanged + BOOST_CHECK_EQUAL( sell_order3_id(db).for_sale.value, 8269 ); // 8268 + 1 + BOOST_CHECK( sell_order3_id(db).sell_price == asset(10000) / asset(34000,usd_id) ); // unchanged + BOOST_CHECK( sell_order3_id(db).expiration == sell_order3_expiration_new ); + BOOST_CHECK( sell_order3_id(db).expiration != sell_order3_expiration ); + BOOST_CHECK( sell_order3_id(db).take_profit_order_id == buy_order5_id ); // unchanged + + // All unchanged + BOOST_REQUIRE_EQUAL( sell_order3_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order3_id(db).on_fill.front().is_type() ); + const auto& action_s3 = sell_order3_id(db).on_fill.front().get(); + BOOST_CHECK( action_s3.fee_asset_id == tpa3.fee_asset_id ); + BOOST_CHECK( action_s3.spread_percent == tpa3.spread_percent ); + BOOST_CHECK( action_s3.size_percent == tpa3.size_percent ); + BOOST_CHECK( action_s3.expiration_seconds == tpa3.expiration_seconds ); + BOOST_CHECK( action_s3.repeat == tpa3.repeat ); + + // The take profit order created by sell_order5 + BOOST_REQUIRE( db.find(buy_order6_id) ); + BOOST_CHECK( buy_order6_id(db).seller == ted_id ); + // sell_order5 gets 3, market fee = round_down(3 * 30 / 10000) = 0, still gets 3 + // take profit order size = round_up(3 * 1 / 10000) = 1 + // Ted USD balance change = 3 - 1 = 2 + BOOST_CHECK_EQUAL( buy_order6_id(db).for_sale.value, 1 ); + // price = (1 / 1) / (1 + 655.35%) = 10000 / 75535 + // min to receive = round_up( 1 * 75535 / 10000 ) = 8 + // updated price = 1 / 8 + BOOST_CHECK( buy_order6_id(db).sell_price == asset(1,usd_id) / asset(8) ); + BOOST_CHECK( buy_order6_id(db).expiration == buy_order6_expiration ); + BOOST_CHECK( !buy_order6_id(db).take_profit_order_id ); + + BOOST_REQUIRE_EQUAL( buy_order6_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( buy_order6_id(db).on_fill.front().is_type() ); + const auto& action_b6 = buy_order6_id(db).on_fill.front().get(); + BOOST_CHECK( action_b6.fee_asset_id == tpa5.fee_asset_id ); + BOOST_CHECK( action_b6.spread_percent == tpa5.spread_percent ); + BOOST_CHECK( action_b6.size_percent == tpa5.size_percent ); + BOOST_CHECK( action_b6.expiration_seconds == tpa5.expiration_seconds ); + BOOST_CHECK( action_b6.repeat == tpa5.repeat ); + + check_balances(); + }; + + check_result_3(); + + generate_block(); + + check_result_3(); + + // Sam places an order to buy CORE with USD with on_fill + // fee_asset, spread, size, expiration, repeat + create_take_profit_order_action tpa6 { core_id, 10, 10000, uint32_t(-1), true }; + vector on_fill_6 { tpa6 }; + + const limit_order_object* buy_order7 = create_sell_order( sam_id, asset(338, usd_id), asset(100), + time_point_sec::maximum(), price::unit_price(), on_fill_6 ); + last_order_id = last_order_id + 1; + + BOOST_REQUIRE( buy_order7 ); + limit_order_id_type buy_order7_id = buy_order7->get_id(); + + BOOST_CHECK( buy_order7_id(db).seller == sam_id ); + BOOST_CHECK_EQUAL( buy_order7_id(db).for_sale.value, 338 ); + BOOST_CHECK( buy_order7_id(db).sell_price == asset(338,usd_id) / asset(100) ); + BOOST_CHECK( buy_order7_id(db).expiration == time_point_sec::maximum() ); + BOOST_CHECK( !buy_order7_id(db).take_profit_order_id ); + + BOOST_REQUIRE_EQUAL( buy_order7_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( buy_order7_id(db).on_fill.front().is_type() ); + const auto& action_b7 = buy_order7_id(db).on_fill.front().get(); + BOOST_CHECK( action_b7.fee_asset_id == tpa6.fee_asset_id ); + BOOST_CHECK( action_b7.spread_percent == tpa6.spread_percent ); + BOOST_CHECK( action_b7.size_percent == tpa6.size_percent ); + BOOST_CHECK( action_b7.expiration_seconds == tpa6.expiration_seconds ); + BOOST_CHECK( action_b7.repeat == tpa6.repeat ); + + expected_balance_sam_usd -= 338; + + check_balances(); + + // Make a whitelist, Sam is not in + { + BOOST_TEST_MESSAGE( "Setting up whitelisting" ); + asset_update_operation uop; + uop.asset_to_update = usd_id; + uop.issuer = usd_id(db).issuer; + uop.new_options = usd_id(db).options; + // The whitelist is managed by Ted + uop.new_options.whitelist_authorities.insert(ted_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Upgrade Ted so that he can manage the whitelist + upgrade_to_lifetime_member( ted_id ); + + // Add Ted to the whitelist, but do not add others + account_whitelist_operation wop; + wop.authorizing_account = ted_id; + wop.account_to_list = ted_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + // Ted sells CORE for USD, fully fills buy_order7, partially fills buy_order5 + const limit_order_object* sell_order7 = create_sell_order( ted_id, asset(200), asset(200, usd_id) ); + last_order_id = last_order_id + 1; + + // sell_order7 is fully filled + BOOST_CHECK( !sell_order7 ); + + expected_balance_sam_core += 200; // See calculation below + expected_balance_ted_core -= 200; // See calculation below + expected_balance_ted_usd += 671; // 336 + 335, See calculation below + + const auto check_result_4 = [&]() + { + // buy_order7 is fully filled + BOOST_CHECK( !db.find(buy_order7_id) ); + // buy_order7 gets 100, pays = round_down(100 * 3380 / 1000) = 338, + // updated gets = round_up( 338 * 1000 / 3380 ) = 100 + + // fails to create a take profit order due to whitelisting + BOOST_CHECK( !db.find(last_order_id+1) ); + + // Ted gets 338 USD, market fee = round_down(338 * 0.8%) = 2, + // updated gets = 338 - 2 = 336 + + // buy_order5 is partially filled + BOOST_REQUIRE( db.find(buy_order5_id) ); + BOOST_CHECK( buy_order5_id(db).seller == sam_id ); + // buy_order5 gets 100, pays round_down(100 * 5855 / 1735) = 337 + // updated gets = round_up(337 * 1735 / 5855) = 100 + BOOST_CHECK_EQUAL( buy_order5_id(db).for_sale.value, 5515 ); // 5852 - 337 + BOOST_CHECK( buy_order5_id(db).sell_price == asset(5855,usd_id) / asset(1735) ); // unchanged + BOOST_CHECK( buy_order5_id(db).expiration == buy_order5_expiration ); // unchanged + BOOST_CHECK( buy_order5_id(db).take_profit_order_id == sell_order3_id ); // unchanged + + // All unchanged + BOOST_REQUIRE_EQUAL( buy_order5_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( buy_order5_id(db).on_fill.front().is_type() ); + const auto& action_b5 = buy_order5_id(db).on_fill.front().get(); + BOOST_CHECK( action_b5.fee_asset_id == tpa3.fee_asset_id ); + BOOST_CHECK( action_b5.spread_percent == tpa3.spread_percent ); + BOOST_CHECK( action_b5.size_percent == tpa3.size_percent ); + BOOST_CHECK( action_b5.expiration_seconds == tpa3.expiration_seconds ); + BOOST_CHECK( action_b5.repeat == tpa3.repeat ); + + // Due to whitelisting, the take profit order linked to buy_order5 (sell_order3) is unchanged + BOOST_REQUIRE( db.find(sell_order3_id) ); + BOOST_CHECK( sell_order3_id(db).seller == sam_id ); + BOOST_CHECK_EQUAL( sell_order3_id(db).for_sale.value, 8269 ); // unchanged + BOOST_CHECK( sell_order3_id(db).sell_price == asset(10000) / asset(34000,usd_id) ); // unchanged + BOOST_CHECK( sell_order3_id(db).expiration == sell_order3_expiration_new ); + BOOST_CHECK( sell_order3_id(db).take_profit_order_id == buy_order5_id ); // unchanged + + // All unchanged + BOOST_REQUIRE_EQUAL( sell_order3_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order3_id(db).on_fill.front().is_type() ); + const auto& action_s3 = sell_order3_id(db).on_fill.front().get(); + BOOST_CHECK( action_s3.fee_asset_id == tpa3.fee_asset_id ); + BOOST_CHECK( action_s3.spread_percent == tpa3.spread_percent ); + BOOST_CHECK( action_s3.size_percent == tpa3.size_percent ); + BOOST_CHECK( action_s3.expiration_seconds == tpa3.expiration_seconds ); + BOOST_CHECK( action_s3.repeat == tpa3.repeat ); + + // Ted gets 337 USD, market fee = round_down(337 * 0.8%) = 2, + // updated gets = 337 - 2 = 335 + + check_balances(); + }; + + check_result_4(); + + generate_block(); + + check_result_4(); + + const asset_object& eur = create_user_issued_asset("MYEUR"); + asset_id_type eur_id = eur.get_id(); + + // Ted buys EUR with USD + const limit_order_object* buy_eur = create_sell_order( ted_id, asset(200, usd_id), asset(200, eur_id) ); + last_order_id = last_order_id + 1; + + limit_order_id_type buy_eur_id = buy_eur->get_id(); + + expected_balance_ted_usd -= 200; + + const auto check_result_5 = [&]() + { + // Check that the failed OSO operation does not increase the internal next value of limit_order_id + BOOST_CHECK( last_order_id == buy_eur_id ); + + check_balances(); + }; + + check_result_5(); + + generate_block(); + + check_result_5(); + + // Sam cancels an order + cancel_limit_order( sell_order3_id(db) ); + + expected_balance_sam_core += 8269; + + const auto check_result_6 = [&]() + { + // buy_order5 is canceled + BOOST_CHECK( !db.find(sell_order3_id) ); + + // The take profit order linked to sell_order3 (buy_order5) is updated + BOOST_REQUIRE( db.find(buy_order5_id) ); + BOOST_CHECK_EQUAL( buy_order5_id(db).for_sale.value, 5515 ); // unchanged + BOOST_CHECK( buy_order5_id(db).sell_price == asset(5855,usd_id) / asset(1735) ); // unchanged + BOOST_CHECK( buy_order5_id(db).expiration == buy_order5_expiration ); // unchanged + BOOST_CHECK( !buy_order5_id(db).take_profit_order_id ); // cleared + + // Others all unchanged + BOOST_REQUIRE_EQUAL( buy_order5_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( buy_order5_id(db).on_fill.front().is_type() ); + const auto& action_b5 = buy_order5_id(db).on_fill.front().get(); + BOOST_CHECK( action_b5.fee_asset_id == tpa3.fee_asset_id ); + BOOST_CHECK( action_b5.spread_percent == tpa3.spread_percent ); + BOOST_CHECK( action_b5.size_percent == tpa3.size_percent ); + BOOST_CHECK( action_b5.expiration_seconds == tpa3.expiration_seconds ); + BOOST_CHECK( action_b5.repeat == tpa3.repeat ); + + check_balances(); + }; + + check_result_6(); + + generate_block(); + + check_result_6(); + +} FC_LOG_AND_RETHROW() } + +/// Tests a scenario where a take profit order fails to be sent due to extreme order price +BOOST_AUTO_TEST_CASE( oso_take_profit_order_fail_test_1 ) +{ try { + + // Proceeds to the hard fork + generate_blocks( HARDFORK_CORE_2535_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)(ted)); + + const asset_object& usd = create_user_issued_asset( "MYUSD" ); + asset_id_type usd_id = usd.get_id(); + asset_id_type core_id; + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( ted, asset(init_amount) ); + + issue_uia( ted, asset(GRAPHENE_MAX_SHARE_SUPPLY, usd_id) ); + + int64_t expected_balance_sam_core = init_amount; + int64_t expected_balance_ted_core = init_amount; + int64_t expected_balance_sam_usd = 0; + int64_t expected_balance_ted_usd = GRAPHENE_MAX_SHARE_SUPPLY; + + const auto& check_balances = [&]() { + BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, expected_balance_sam_core ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, core_id ).amount.value, expected_balance_ted_core ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, expected_balance_sam_usd ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, usd_id ).amount.value, expected_balance_ted_usd ); + }; + + check_balances(); + + // Ted buys CORE with USD with on_fill + // fee_asset, spread, size, expiration, repeat + create_take_profit_order_action tpa1 { core_id, 500, 10000, 3600, false }; + vector on_fill_1 { tpa1 }; + + const limit_order_object* sell_order1 = create_sell_order( ted_id, asset(GRAPHENE_MAX_SHARE_SUPPLY, usd_id), + asset(100), time_point_sec::maximum(), + price::unit_price(), on_fill_1 ); + BOOST_REQUIRE( sell_order1 ); + limit_order_id_type sell_order1_id = sell_order1->get_id(); + + limit_order_id_type last_order_id = sell_order1_id; + + BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); + + BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type() ); + const auto& action_s1 = sell_order1_id(db).on_fill.front().get(); + BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id ); + BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent ); + BOOST_CHECK( action_s1.size_percent == tpa1.size_percent ); + BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds ); + BOOST_CHECK( action_s1.repeat == tpa1.repeat ); + + expected_balance_ted_usd -= GRAPHENE_MAX_SHARE_SUPPLY; + check_balances(); + + // Sam sells CORE for USD without on_fill, fully fills Ted's order + const limit_order_object* buy_order1 = create_sell_order( sam_id, asset(100), + asset(GRAPHENE_MAX_SHARE_SUPPLY, usd_id) ); + last_order_id = last_order_id + 1; + + // The buy order gets fully filled + BOOST_CHECK( !buy_order1 ); + + expected_balance_sam_core -= 100; + expected_balance_sam_usd += GRAPHENE_MAX_SHARE_SUPPLY; + + expected_balance_ted_core += 100; + + const auto& check_result_1 = [&]() + { + // The sell order is fully filled + BOOST_CHECK( !db.find(sell_order1_id) ); + + // The take profit order is not created due to an exception + BOOST_CHECK( !db.find(last_order_id+1) ); + + check_balances(); + }; + + check_result_1(); + + generate_block(); + + check_result_1(); + + // Sam sells more CORE for USD without on_fill + const limit_order_object* sell_order2 = create_sell_order( sam_id, asset(10000), asset(13000, usd_id) ); + last_order_id = last_order_id + 1; + + BOOST_REQUIRE( sell_order2 ); + limit_order_id_type sell_order2_id = sell_order2->get_id(); + + expected_balance_sam_core -= 10000; + + const auto check_result_2 = [&]() + { + // Check that the failed OSO operation does not increase the internal next value of limit_order_id + BOOST_CHECK( last_order_id == sell_order2_id ); + + check_balances(); + }; + + check_result_2(); + + generate_block(); + + check_result_2(); + +} FC_LOG_AND_RETHROW() } + +/// Tests OSO-related order updates: basic operation validation and evaluation +BOOST_AUTO_TEST_CASE( oso_take_profit_order_update_basic_test ) +{ try { + + // Proceeds to the hard fork + generate_blocks( HARDFORK_CORE_2535_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)); + + const asset_object& usd = create_user_issued_asset( "MYUSD" ); + asset_id_type usd_id = usd.get_id(); + asset_id_type core_id; + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + + // Sam sells CORE for USD with on_fill + // fee_asset, spread, size, expiration, repeat + create_take_profit_order_action tpa1 { core_id, 100, 10000, 3600, false }; + vector on_fill_1 { tpa1 }; + + const limit_order_object* sell_order1 = create_sell_order( sam_id, asset(10000), asset(12345, usd_id), + time_point_sec::maximum(), price::unit_price(), on_fill_1 ); + BOOST_REQUIRE( sell_order1 ); + limit_order_id_type sell_order1_id = sell_order1->get_id(); + + // Sam tries to update a limit order + + // Spread percentage should be positive + // fee_asset, spread, size, expiration, repeat + tpa1 = { core_id, 0, 10000, 3600, false }; + on_fill_1 = { tpa1 }; + BOOST_CHECK_THROW( update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_1 ), + fc::exception ); + // Cannot propose either + limit_order_update_operation uop1 = make_limit_order_update_op( sam_id, sell_order1_id, {}, {}, {}, on_fill_1 ); + BOOST_CHECK_THROW( propose( uop1 ), fc::exception ); + + // Size percentage should be positive + // fee_asset, spread, size, expiration, repeat + tpa1 = { core_id, 1, 0, 3600, false }; + on_fill_1 = { tpa1 }; + BOOST_CHECK_THROW( update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_1 ), + fc::exception ); + // Cannot propose either + uop1 = make_limit_order_update_op( sam_id, sell_order1_id, {}, {}, {}, on_fill_1 ); + BOOST_CHECK_THROW( propose( uop1 ), fc::exception ); + + // Size percentage should not exceed 100% + // fee_asset, spread, size, expiration, repeat + tpa1 = { core_id, 1, 10001, 3600, false }; + on_fill_1 = { tpa1 }; + BOOST_CHECK_THROW( update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_1 ), + fc::exception ); + // Cannot propose either + uop1 = make_limit_order_update_op( sam_id, sell_order1_id, {}, {}, {}, on_fill_1 ); + BOOST_CHECK_THROW( propose( uop1 ), fc::exception ); + + // Expiration should be positive + // fee_asset, spread, size, expiration, repeat + tpa1 = { core_id, 1, 10000, 0, false }; + on_fill_1 = { tpa1 }; + BOOST_CHECK_THROW( update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_1 ), + fc::exception ); + // Cannot propose either + uop1 = make_limit_order_update_op( sam_id, sell_order1_id, {}, {}, {}, on_fill_1 ); + BOOST_CHECK_THROW( propose( uop1 ), fc::exception ); + + // Fee asset should exist + tpa1 = { usd_id + 1, 1, GRAPHENE_100_PERCENT, 3600, false }; + on_fill_1 = { tpa1 }; + BOOST_CHECK_THROW( update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_1 ), + fc::exception ); + // Can propose + uop1 = make_limit_order_update_op( sam_id, sell_order1_id, {}, {}, {}, on_fill_1 ); + propose( uop1 ); + + // on_fill must contain 0 or 1 action + tpa1 = { core_id, 1, GRAPHENE_100_PERCENT, 3600, false }; + on_fill_1 = { tpa1, tpa1 }; + BOOST_CHECK_THROW( update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_1 ), + fc::exception ); + // Can propose + uop1 = make_limit_order_update_op( sam_id, sell_order1_id, {}, {}, {}, on_fill_1 ); + propose( uop1 ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +/// Tests OSO-related order updates, scenarios: +/// * update an order which is not linked to another order and has no on_fill +/// * add on_fill +/// * update an order which is not linked to another order and has on_fill +/// * update on_fill +/// * remove on_fill +BOOST_AUTO_TEST_CASE( oso_take_profit_order_update_test_1 ) +{ try { + + // Proceeds to the hard fork + generate_blocks( HARDFORK_CORE_2535_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)); + + const asset_object& usd = create_user_issued_asset( "MYUSD" ); + asset_id_type usd_id = usd.get_id(); + asset_id_type core_id; + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + + int64_t expected_balance_sam_core = init_amount; + int64_t expected_balance_sam_usd = 0; + + const auto& check_balances = [&]() { + BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, expected_balance_sam_core ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, expected_balance_sam_usd ); + }; + + // Sam sells CORE for USD without on_fill + const limit_order_object* sell_order1 = create_sell_order( sam_id, asset(10000), asset(12345, usd_id) ); + BOOST_REQUIRE( sell_order1 ); + limit_order_id_type sell_order1_id = sell_order1->get_id(); + + BOOST_CHECK( sell_order1_id(db).on_fill.empty() ); + BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); + + expected_balance_sam_core -= 10000; + check_balances(); + + // Sam updates order with on_fill + // fee_asset, spread, size, expiration, repeat + create_take_profit_order_action tpa1 { core_id, 100, 10000, 3600, false }; + vector on_fill_1 { tpa1 }; + update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_1 ); + + const auto& check_result_1 = [&]() + { + BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); + + BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type() ); + const auto& action_s1 = sell_order1_id(db).on_fill.front().get(); + BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id ); + BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent ); + BOOST_CHECK( action_s1.size_percent == tpa1.size_percent ); + BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds ); + BOOST_CHECK( action_s1.repeat == tpa1.repeat ); + + check_balances(); + }; + + check_result_1(); + + generate_block(); + + check_result_1(); + + // Sam updates order with new on_fill + // fee_asset, spread, size, expiration, repeat + create_take_profit_order_action tpa2 { usd_id, 10, 1000, 3800, true }; + vector on_fill_2 { tpa2 }; + update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_2 ); + + const auto& check_result_2 = [&]() + { + BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); + + BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type() ); + const auto& action_s1 = sell_order1_id(db).on_fill.front().get(); + BOOST_CHECK( action_s1.fee_asset_id == tpa2.fee_asset_id ); + BOOST_CHECK( action_s1.spread_percent == tpa2.spread_percent ); + BOOST_CHECK( action_s1.size_percent == tpa2.size_percent ); + BOOST_CHECK( action_s1.expiration_seconds == tpa2.expiration_seconds ); + BOOST_CHECK( action_s1.repeat == tpa2.repeat ); + + check_balances(); + }; + + check_result_2(); + + generate_block(); + + check_result_2(); + + // Sam updates order without on_fill + update_limit_order( sell_order1_id, {}, asset(1) ); + expected_balance_sam_core -= 1; + + const auto& check_result_3 = [&]() + { + BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); + + BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type() ); + const auto& action_s1 = sell_order1_id(db).on_fill.front().get(); + BOOST_CHECK( action_s1.fee_asset_id == tpa2.fee_asset_id ); + BOOST_CHECK( action_s1.spread_percent == tpa2.spread_percent ); + BOOST_CHECK( action_s1.size_percent == tpa2.size_percent ); + BOOST_CHECK( action_s1.expiration_seconds == tpa2.expiration_seconds ); + BOOST_CHECK( action_s1.repeat == tpa2.repeat ); + + check_balances(); + }; + + check_result_3(); + + generate_block(); + + check_result_3(); + + // Sam updates order with an empty on_fill + vector on_fill_3; + update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_3 ); + + const auto& check_result_4 = [&]() + { + BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); + + BOOST_CHECK( sell_order1_id(db).on_fill.empty() ); + + check_balances(); + }; + + check_result_4(); + + generate_block(); + + check_result_4(); + +} FC_LOG_AND_RETHROW() } + +/// Tests OSO-related order updates, scenarios: +/// * update an order which is linked to another order but has no on_fill +/// * do not add on_fill, do not specify a new price +/// * do not add on_fill, specify a new price but no change +/// * do not add on_fill, update price +/// * add on_fill +/// * update an order which is linked to another order and has on_fill +/// * do not specify new on_fill, do not specify a new price +/// * do not specify new on_fill, specify a new price but no change +/// * do not specify new on_fill, update price +/// * remove on_fill +/// * update on_fill +/// * do not update spread_percent or repeat +/// * update spread_percent +/// * update repeat +BOOST_AUTO_TEST_CASE( oso_take_profit_order_update_test_2 ) +{ try { + + // Proceeds to the hard fork + generate_blocks( HARDFORK_CORE_2535_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)(ted)); + + additional_asset_options_t usd_options; + usd_options.value.taker_fee_percent = 80; // 0.8% taker fee + + const asset_object& usd = create_user_issued_asset( "MYUSD", ted, charge_market_fee | white_list, + price(asset(1, asset_id_type(1)), asset(1)), + 4, 30, usd_options ); // 0.3% maker fee + asset_id_type usd_id = usd.get_id(); + asset_id_type core_id; + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( ted, asset(init_amount) ); + issue_uia( ted, asset(init_amount, usd_id) ); + + int64_t expected_balance_sam_core = init_amount; + int64_t expected_balance_ted_core = init_amount; + int64_t expected_balance_sam_usd = 0; + int64_t expected_balance_ted_usd = init_amount; + + const auto& check_balances = [&]() { + BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, expected_balance_sam_core ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, core_id ).amount.value, expected_balance_ted_core ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, expected_balance_sam_usd ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, usd_id ).amount.value, expected_balance_ted_usd ); + }; + + check_balances(); + + // Sam sells CORE for USD with on_fill + // fee_asset, spread, size, expiration, repeat + create_take_profit_order_action tpa1 { core_id, 100, 10000, 3600, false }; + vector on_fill_1 { tpa1 }; + + const limit_order_object* sell_order1 = create_sell_order( sam_id, asset(10000), asset(12345, usd_id), + time_point_sec::maximum(), price::unit_price(), on_fill_1 ); + BOOST_REQUIRE( sell_order1 ); + limit_order_id_type sell_order1_id = sell_order1->get_id(); + + limit_order_id_type last_order_id = sell_order1_id; + + BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 10000 ); + BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) ); + BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); + + BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type() ); + { + const auto& action_s1 = sell_order1_id(db).on_fill.front().get(); + BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id ); + BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent ); + BOOST_CHECK( action_s1.size_percent == tpa1.size_percent ); + BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds ); + BOOST_CHECK( action_s1.repeat == tpa1.repeat ); + } + + expected_balance_sam_core -= 10000; + check_balances(); + + // Ted buys CORE with USD without on_fill, partially fills Sam's order + const limit_order_object* buy_order1 = create_sell_order( ted_id, asset(1235, usd_id), asset(1000) ); + last_order_id = last_order_id + 1; + + // The buy order is smaller, it gets fully filled + BOOST_CHECK( !buy_order1 ); + expected_balance_ted_core += 1000; + expected_balance_ted_usd -= 1235; + + // The newly created take profit order is a buy order + last_order_id = last_order_id + 1; + limit_order_id_type buy_order2_id = last_order_id; + + auto buy_order2_expiration = db.head_block_time() + 3600; + + const auto& check_result_1 = [&]() + { + // The sell order is partially filled + BOOST_REQUIRE( db.find(sell_order1_id) ); + // The take profit order + BOOST_REQUIRE( db.find(buy_order2_id) ); + BOOST_CHECK( buy_order2_id(db).seller == sam_id ); + // The sell order gets 1235, market fee = round_down(1235 * 30 / 10000) = 3 + BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 ); // 1235 - 3 + // price = (12345 / 10000) / 101% = 12345 / 10100 + // min to receive = round_up( 1232 * 10100 / 12345 ) = 1008 + // updated price = 1232 / 1008 + BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) ); + BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration ); + BOOST_CHECK( buy_order2_id(db).take_profit_order_id == sell_order1_id ); + BOOST_CHECK( buy_order2_id(db).on_fill.empty() ); + + // The sell order is partially filled, pays 1000 + BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 ); // 10000 - 1000 + BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) ); + BOOST_CHECK( sell_order1_id(db).take_profit_order_id == buy_order2_id ); + + check_balances(); + }; + + check_result_1(); + + generate_block(); + + check_result_1(); + + // Several passes to test different scenarios + auto bak_balance_sam_core = expected_balance_sam_core; + auto bak_balance_sam_usd = expected_balance_sam_usd; + for( size_t i = 0; i <= 10; ++i ) + { + // Sam updates order + create_take_profit_order_action tpa2 = tpa1; + if( 0 == i ) + { + // no on_fill, do not add on_fill, do not specify a new price + update_limit_order( buy_order2_id, {}, asset(-1, usd_id) ); + expected_balance_sam_usd += 1; + } + else if( 1 == i ) + { + // no on_fill, do not add on_fill, specify a new price but no change + update_limit_order( buy_order2_id, buy_order2_id(db).sell_price, {}, time_point_sec::maximum() ); + } + else if( 2 == i ) + { + // no on_fill, do not add on_fill, update price + auto new_price = buy_order2_id(db).sell_price; + new_price.quote.amount += 1; + update_limit_order( buy_order2_id, new_price ); + } + else if( 3 == i ) + { + // no on_fill, add on_fill + update_limit_order( buy_order2_id, {}, {}, {}, price::unit_price(), on_fill_1 ); + } + else if( 4 == i ) + { + // has on_fill, do not specify new on_fill, do not specify a new price + update_limit_order( sell_order1_id, {}, asset(1) ); + expected_balance_sam_core -= 1; + } + else if( 5 == i ) + { + // has on_fill, do not specify new on_fill, specify a new price but no change + update_limit_order( sell_order1_id, sell_order1_id(db).sell_price, asset(1) ); + expected_balance_sam_core -= 1; + } + else if( 6 == i ) + { + // has on_fill, do not specify new on_fill, update price + auto new_price = sell_order1_id(db).sell_price; + new_price.quote.amount += 1; + update_limit_order( sell_order1_id, new_price ); + } + else if( 7 == i ) + { + // has on_fill, specify an empty new on_fill (to remove it) + vector on_fill_2; + update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_2 ); + } + else if( 8 == i ) + { + // has on_fill, specify a new on_fill, but no update to spread_percent or repeat + // fee_asset, spread, size, expiration, repeat + tpa2 = { usd_id, 100, 9000, 7200, false }; + vector on_fill_2 { tpa2 }; + update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_2 ); + } + else if( 9 == i ) + { + // has on_fill, specify a new on_fill, update spread_percent + // fee_asset, spread, size, expiration, repeat + tpa2 = { core_id, 101, 10000, 3600, false }; + vector on_fill_2 { tpa2 }; + update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_2 ); + } + else if( 10 == i ) + { + // has on_fill, specify a new on_fill, update repeat + // fee_asset, spread, size, expiration, repeat + tpa2 = { core_id, 100, 10000, 3600, true }; + vector on_fill_2 { tpa2 }; + update_limit_order( sell_order1_id, {}, {}, {}, price::unit_price(), on_fill_2 ); + } + + const auto& check_result_2 = [&]() + { + if( 0 == i ) + { + // The sell order + BOOST_REQUIRE( db.find(sell_order1_id) ); + BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 ); + BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) ); + BOOST_CHECK( sell_order1_id(db).take_profit_order_id == buy_order2_id ); + + BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type() ); + const auto& action_s1 = sell_order1_id(db).on_fill.front().get(); + BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id ); + BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent ); + BOOST_CHECK( action_s1.size_percent == tpa1.size_percent ); + BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds ); + BOOST_CHECK( action_s1.repeat == tpa1.repeat ); + + // The take profit order + BOOST_REQUIRE( db.find(buy_order2_id) ); + BOOST_CHECK( buy_order2_id(db).seller == sam_id ); + BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1231 ); // updated: 1232 - 1 + BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) ); + BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration ); + BOOST_CHECK( buy_order2_id(db).take_profit_order_id == sell_order1_id ); + BOOST_CHECK( buy_order2_id(db).on_fill.empty() ); + } + else if( 1 == i ) + { + // The sell order + BOOST_REQUIRE( db.find(sell_order1_id) ); + BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 ); + BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) ); + BOOST_CHECK( sell_order1_id(db).take_profit_order_id == buy_order2_id ); + + BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type() ); + const auto& action_s1 = sell_order1_id(db).on_fill.front().get(); + BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id ); + BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent ); + BOOST_CHECK( action_s1.size_percent == tpa1.size_percent ); + BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds ); + BOOST_CHECK( action_s1.repeat == tpa1.repeat ); + + // The take profit order + BOOST_REQUIRE( db.find(buy_order2_id) ); + BOOST_CHECK( buy_order2_id(db).seller == sam_id ); + BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 ); + BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) ); + BOOST_CHECK( buy_order2_id(db).expiration == time_point_sec::maximum() ); // updated + BOOST_CHECK( buy_order2_id(db).take_profit_order_id == sell_order1_id ); + BOOST_CHECK( buy_order2_id(db).on_fill.empty() ); + } + else if( 2 == i ) + { + // The sell order + BOOST_REQUIRE( db.find(sell_order1_id) ); + BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 ); + BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) ); + BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); // cleared + + BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type() ); + const auto& action_s1 = sell_order1_id(db).on_fill.front().get(); + BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id ); + BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent ); + BOOST_CHECK( action_s1.size_percent == tpa1.size_percent ); + BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds ); + BOOST_CHECK( action_s1.repeat == tpa1.repeat ); + + // The take profit order + BOOST_REQUIRE( db.find(buy_order2_id) ); + BOOST_CHECK( buy_order2_id(db).seller == sam_id ); + BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 ); + BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1009) ); // updated + BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration ); + BOOST_CHECK( !buy_order2_id(db).take_profit_order_id ); // cleared + BOOST_CHECK( buy_order2_id(db).on_fill.empty() ); + } + else if( 3 == i ) + { + // The sell order + BOOST_REQUIRE( db.find(sell_order1_id) ); + BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 ); + BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) ); + BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); // cleared + + BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type() ); + const auto& action_s1 = sell_order1_id(db).on_fill.front().get(); + BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id ); + BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent ); + BOOST_CHECK( action_s1.size_percent == tpa1.size_percent ); + BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds ); + BOOST_CHECK( action_s1.repeat == tpa1.repeat ); + + // The take profit order + BOOST_REQUIRE( db.find(buy_order2_id) ); + BOOST_CHECK( buy_order2_id(db).seller == sam_id ); + BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 ); + BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) ); + BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration ); + BOOST_CHECK( !buy_order2_id(db).take_profit_order_id ); // cleared + + BOOST_REQUIRE_EQUAL( buy_order2_id(db).on_fill.size(), 1U ); // updated + BOOST_REQUIRE( buy_order2_id(db).on_fill.front().is_type() ); + const auto& action_b2 = buy_order2_id(db).on_fill.front().get(); + BOOST_CHECK( action_b2.fee_asset_id == tpa1.fee_asset_id ); + BOOST_CHECK( action_b2.spread_percent == tpa1.spread_percent ); + BOOST_CHECK( action_b2.size_percent == tpa1.size_percent ); + BOOST_CHECK( action_b2.expiration_seconds == tpa1.expiration_seconds ); + BOOST_CHECK( action_b2.repeat == tpa1.repeat ); + } + else if( 4 == i ) + { + // The sell order + BOOST_REQUIRE( db.find(sell_order1_id) ); + BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9001 ); // updated: 9000 + 1 + BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) ); + BOOST_CHECK( sell_order1_id(db).take_profit_order_id == buy_order2_id ); + + BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type() ); + const auto& action_s1 = sell_order1_id(db).on_fill.front().get(); + BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id ); + BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent ); + BOOST_CHECK( action_s1.size_percent == tpa1.size_percent ); + BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds ); + BOOST_CHECK( action_s1.repeat == tpa1.repeat ); + + // The take profit order + BOOST_REQUIRE( db.find(buy_order2_id) ); + BOOST_CHECK( buy_order2_id(db).seller == sam_id ); + BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 ); + BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) ); + BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration ); + BOOST_CHECK( buy_order2_id(db).take_profit_order_id == sell_order1_id ); + BOOST_CHECK( buy_order2_id(db).on_fill.empty() ); + } + else if( 5 == i ) + { + // The sell order + BOOST_REQUIRE( db.find(sell_order1_id) ); + BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9001 ); // updated: 9000 + 1 + BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) ); + BOOST_CHECK( sell_order1_id(db).take_profit_order_id == buy_order2_id ); + + BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type() ); + const auto& action_s1 = sell_order1_id(db).on_fill.front().get(); + BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id ); + BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent ); + BOOST_CHECK( action_s1.size_percent == tpa1.size_percent ); + BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds ); + BOOST_CHECK( action_s1.repeat == tpa1.repeat ); + + // The take profit order + BOOST_REQUIRE( db.find(buy_order2_id) ); + BOOST_CHECK( buy_order2_id(db).seller == sam_id ); + BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 ); + BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) ); + BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration ); + BOOST_CHECK( buy_order2_id(db).take_profit_order_id == sell_order1_id ); + BOOST_CHECK( buy_order2_id(db).on_fill.empty() ); + } + else if( 6 == i ) + { + // The sell order + BOOST_REQUIRE( db.find(sell_order1_id) ); + BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 ); + BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12346, usd_id) ); // updated + BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); // cleared + + BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type() ); + const auto& action_s1 = sell_order1_id(db).on_fill.front().get(); + BOOST_CHECK( action_s1.fee_asset_id == tpa1.fee_asset_id ); + BOOST_CHECK( action_s1.spread_percent == tpa1.spread_percent ); + BOOST_CHECK( action_s1.size_percent == tpa1.size_percent ); + BOOST_CHECK( action_s1.expiration_seconds == tpa1.expiration_seconds ); + BOOST_CHECK( action_s1.repeat == tpa1.repeat ); + + // The take profit order + BOOST_REQUIRE( db.find(buy_order2_id) ); + BOOST_CHECK( buy_order2_id(db).seller == sam_id ); + BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 ); + BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) ); + BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration ); + BOOST_CHECK( !buy_order2_id(db).take_profit_order_id ); // cleared + BOOST_CHECK( buy_order2_id(db).on_fill.empty() ); + } + else if( 7 == i ) + { + // The sell order + BOOST_REQUIRE( db.find(sell_order1_id) ); + BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 ); + BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) ); + BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); // cleared + + BOOST_CHECK( sell_order1_id(db).on_fill.empty() ); // removed + + // The take profit order + BOOST_REQUIRE( db.find(buy_order2_id) ); + BOOST_CHECK( buy_order2_id(db).seller == sam_id ); + BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 ); + BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) ); + BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration ); + BOOST_CHECK( !buy_order2_id(db).take_profit_order_id ); // cleared + BOOST_CHECK( buy_order2_id(db).on_fill.empty() ); + } + else if( 8 == i ) + { + // The sell order + BOOST_REQUIRE( db.find(sell_order1_id) ); + BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 ); + BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) ); + BOOST_CHECK( sell_order1_id(db).take_profit_order_id == buy_order2_id ); + + BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type() ); + const auto& action_s1 = sell_order1_id(db).on_fill.front().get(); + BOOST_CHECK( action_s1.fee_asset_id == tpa2.fee_asset_id ); // updated + BOOST_CHECK( action_s1.spread_percent == tpa2.spread_percent ); + BOOST_CHECK( action_s1.size_percent == tpa2.size_percent ); // updated + BOOST_CHECK( action_s1.expiration_seconds == tpa2.expiration_seconds ); // updated + BOOST_CHECK( action_s1.repeat == tpa2.repeat ); + + // The take profit order + BOOST_REQUIRE( db.find(buy_order2_id) ); + BOOST_CHECK( buy_order2_id(db).seller == sam_id ); + BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 ); + BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) ); + BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration ); + BOOST_CHECK( buy_order2_id(db).take_profit_order_id == sell_order1_id ); + BOOST_CHECK( buy_order2_id(db).on_fill.empty() ); + } + else if( 9 == i ) + { + // The sell order + BOOST_REQUIRE( db.find(sell_order1_id) ); + BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 ); + BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) ); + BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); // cleared + + BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type() ); + const auto& action_s1 = sell_order1_id(db).on_fill.front().get(); + BOOST_CHECK( action_s1.fee_asset_id == tpa2.fee_asset_id ); + BOOST_CHECK( action_s1.spread_percent == tpa2.spread_percent ); // updated + BOOST_CHECK( action_s1.size_percent == tpa2.size_percent ); + BOOST_CHECK( action_s1.expiration_seconds == tpa2.expiration_seconds ); + BOOST_CHECK( action_s1.repeat == tpa2.repeat ); + + // The take profit order + BOOST_REQUIRE( db.find(buy_order2_id) ); + BOOST_CHECK( buy_order2_id(db).seller == sam_id ); + BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 ); + BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) ); + BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration ); + BOOST_CHECK( !buy_order2_id(db).take_profit_order_id ); // cleared + BOOST_CHECK( buy_order2_id(db).on_fill.empty() ); + } + else if( 10 == i ) + { + // The sell order + BOOST_REQUIRE( db.find(sell_order1_id) ); + BOOST_CHECK_EQUAL( sell_order1_id(db).for_sale.value, 9000 ); + BOOST_CHECK( sell_order1_id(db).sell_price == asset(10000) / asset(12345, usd_id) ); + BOOST_CHECK( !sell_order1_id(db).take_profit_order_id ); // cleared + + BOOST_REQUIRE_EQUAL( sell_order1_id(db).on_fill.size(), 1U ); + BOOST_REQUIRE( sell_order1_id(db).on_fill.front().is_type() ); + const auto& action_s1 = sell_order1_id(db).on_fill.front().get(); + BOOST_CHECK( action_s1.fee_asset_id == tpa2.fee_asset_id ); + BOOST_CHECK( action_s1.spread_percent == tpa2.spread_percent ); + BOOST_CHECK( action_s1.size_percent == tpa2.size_percent ); + BOOST_CHECK( action_s1.expiration_seconds == tpa2.expiration_seconds ); + BOOST_CHECK( action_s1.repeat == tpa2.repeat ); // updated + + // The take profit order + BOOST_REQUIRE( db.find(buy_order2_id) ); + BOOST_CHECK( buy_order2_id(db).seller == sam_id ); + BOOST_CHECK_EQUAL( buy_order2_id(db).for_sale.value, 1232 ); + BOOST_CHECK( buy_order2_id(db).sell_price == asset(1232,usd_id) / asset(1008) ); + BOOST_CHECK( buy_order2_id(db).expiration == buy_order2_expiration ); + BOOST_CHECK( !buy_order2_id(db).take_profit_order_id ); // cleared + BOOST_CHECK( buy_order2_id(db).on_fill.empty() ); + } + + check_balances(); + + }; + + check_result_2(); + + generate_block(); + + check_result_2(); + + // reset + db.pop_block(); + expected_balance_sam_core = bak_balance_sam_core; + expected_balance_sam_usd = bak_balance_sam_usd; + } + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/pob_tests.cpp b/tests/tests/pob_tests.cpp index 872e6cf342..4de88be106 100644 --- a/tests/tests/pob_tests.cpp +++ b/tests/tests/pob_tests.cpp @@ -121,11 +121,11 @@ BOOST_AUTO_TEST_CASE( validation_and_basic_logic_test ) { auto& fee_params = gpo.parameters.get_mutable_fees().parameters; - auto itr = fee_params.find( ticket_create_operation::fee_parameters_type() ); - itr->get().fee = 1; + auto itr = fee_params.find( ticket_create_operation::fee_params_t() ); + itr->get().fee = 1; - itr = fee_params.find( ticket_update_operation::fee_parameters_type() ); - itr->get().fee = 2; + itr = fee_params.find( ticket_update_operation::fee_params_t() ); + itr->get().fee = 2; }); int64_t expected_balance = init_amount; diff --git a/tests/tests/settle_tests.cpp b/tests/tests/settle_tests.cpp index 5fef6aeede..2cb8292899 100644 --- a/tests/tests/settle_tests.cpp +++ b/tests/tests/settle_tests.cpp @@ -1551,7 +1551,7 @@ BOOST_AUTO_TEST_CASE( create_bitassets ) transfer( committee_account, rachelregistrar_id, asset( 10000000 ) ); transfer( committee_account, rachelreferrer_id, asset( 10000000 ) ); transfer( committee_account, rachel.get_id(), asset( 10000000) ); - transfer( committee_account, paul_id, asset( 10000000000 ) ); + transfer( committee_account, paul_id, asset( 10000000000LL ) ); asset_update_operation op; op.issuer = biteur.issuer; @@ -1888,6 +1888,309 @@ BOOST_AUTO_TEST_CASE( market_fee_of_instant_settle_order_after_hardfork_1780 ) } FC_LOG_AND_RETHROW() } +/// Tests instant settlement: +/// * After hf core-2591, forced-settlements are filled at margin call order price (MCOP) when applicable +BOOST_AUTO_TEST_CASE( collateral_fee_of_instant_settlement_test ) +{ try { + + // Advance to a recent hard fork + generate_blocks(HARDFORK_CORE_2582_TIME); + generate_block(); + + // multiple passes, + // i == 0 : before hf core-2591, extreme feed price + // i == 1 : before hf core-2591, normal feed price + // i == 2 : before hf core-2591, normal feed price, and price recovers after GS + // i == 3 : after hf core-2591, extreme feed price + // i == 4 : after hf core-2591, normal feed price + // i == 5 : after hf core-2591, normal feed price, and price recovers after GS + for( int i = 0; i < 6; ++ i ) + { + idump( (i) ); + + if( 3 == i ) + { + // Advance to core-2591 hard fork + generate_blocks(HARDFORK_CORE_2591_TIME); + generate_block(); + } + + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.get_id(); + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::global_settlement ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->get_id(); + + // Transfer funds to sellers + transfer( borrower, seller, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // publish a new feed so that borrower's debt position is undercollateralized + if( 0 == i || 3 == i ) + f.settlement_price = price( asset(1,mpa_id), asset(GRAPHENE_MAX_SHARE_SUPPLY) ); + else + f.settlement_price = price( asset(100,mpa_id), asset(2) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // fund receives 1600 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).settlement_fund.value, 1600 ); + + BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 400 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).settlement_price + == price( asset(100000,mpa_id), asset(1600) ) ); + + BOOST_CHECK( !db.find( call_id ) ); + + if( 2 == i || 5 == i ) + { + // price recovers + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + // call pays price (MSSP) = 100:1 * 1000:1250 = 100000:1250 = 80 + // call match price (MCOP) = 100:1 * 1000:1239 = 100000:1239 = 80.710250202 + publish_feed( mpa_id, feeder_id, f, feed_icr ); + } + + // seller settles + share_type amount_to_settle = 56789; + auto result = force_settle( seller, asset(amount_to_settle, mpa_id) ); + auto op_result = result.get().value; + + auto check_result = [&] + { + BOOST_CHECK( !op_result.new_objects.valid() ); // force settlement order not created + + if( 5 == i ) + { + // settlement fund pays = round_down(56789 * 1600 / 100000) = 908 + // seller pays = round_up(908 * 100000 / 1600) = 56750 + // settlement fund = 1600 - 908 = 692 + // settlement debt = 100000 - 56750 = 43250 + // seller would receive = round_up(56750 * 1239 / 10000 ) = 704 (<908, so ok) + // collateral fee = 908 - 704 = 204 + BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() ); + BOOST_CHECK( *op_result.paid->begin() == asset( 56750, mpa_id ) ); + BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() ); + BOOST_CHECK( *op_result.received->begin() == asset( 704 ) ); + BOOST_REQUIRE( op_result.fees.valid() && 2U == op_result.fees->size() ); + BOOST_CHECK( *op_result.fees->begin() == asset( 204 ) ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).settlement_fund.value, 692 ); + + BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 604 ); // 400 + 204 + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 43250 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 704 ); + } + else + { + // receives = round_down(56789 * 1600 / 100000) = 908 + // pays = round_up(908 * 100000 / 1600) = 56750 + // settlement fund = 1600 - 908 = 692 + // settlement debt = 100000 - 56750 = 43250 + BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() ); + BOOST_CHECK( *op_result.paid->begin() == asset( 56750, mpa_id ) ); + BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() ); + BOOST_CHECK( *op_result.received->begin() == asset( 908 ) ); + BOOST_REQUIRE( op_result.fees.valid() && 1U == op_result.fees->size() ); + BOOST_CHECK( *op_result.fees->begin() == asset( 0 ) ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).settlement_fund.value, 692 ); + + BOOST_CHECK( mpa_id(db).dynamic_data(db).accumulated_collateral_fees == 400 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 43250 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 908 ); + } + + }; + + check_result(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + check_result(); + + // reset + db.pop_block(); + + } // for i + +} FC_LOG_AND_RETHROW() } + +/// Tests instant settlement: +/// * After hf core-2591, for prediction markets, forced-settlements are NOT filled at margin call order price (MCOP) +BOOST_AUTO_TEST_CASE( pm_instant_settlement_price_test ) +{ try { + + // Advance to a recent hard fork + generate_blocks(HARDFORK_CORE_2582_TIME); + generate_block(); + + // multiple passes, + // i == 0 : before hf core-2591 + // i == 1 : after hf core-2591 + for( int i = 0; i < 2; ++ i ) + { + idump( (i) ); + + if( 1 == i ) + { + // Advance to core-2591 hard fork + generate_blocks(HARDFORK_CORE_2591_TIME); + generate_block(); + } + + set_expiration( db, trx ); + + ACTORS((judge)(alice)(feeder)); + + const auto& pmark = create_prediction_market("PMARK", judge_id); + const auto& core = asset_id_type()(db); + + asset_id_type pm_id = pmark.get_id(); + + int64_t init_balance(1000000); + transfer(committee_account, judge_id, asset(init_balance)); + transfer(committee_account, alice_id, asset(init_balance)); + + BOOST_TEST_MESSAGE( "Open position with equal collateral" ); + borrow( alice, pmark.amount(1000), asset(1000) ); + + BOOST_CHECK_EQUAL( get_balance( alice_id, pm_id ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( alice_id, asset_id_type() ), init_balance - 1000 ); + + // add a price feed publisher and publish a feed + update_feed_producers( pm_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,pm_id), asset(1) ); + f.core_exchange_rate = price( asset(100,pm_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( pm_id, feeder_id, f, feed_icr ); + + BOOST_CHECK_EQUAL( get_balance( alice_id, pm_id ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( alice_id, asset_id_type() ), init_balance - 1000 ); + + BOOST_TEST_MESSAGE( "Globally settling" ); + force_global_settle( pmark, pmark.amount(1) / core.amount(1) ); + + BOOST_CHECK_EQUAL( get_balance( alice_id, pm_id ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( alice_id, asset_id_type() ), init_balance - 1000 ); + + // alice settles + auto result = force_settle( alice, asset(300, pm_id) ); + auto op_result = result.get().value; + + BOOST_CHECK( !op_result.new_objects.valid() ); // force settlement order not created + + BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() ); + BOOST_CHECK( *op_result.paid->begin() == asset( 300, pm_id ) ); + BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() ); + BOOST_CHECK( *op_result.received->begin() == asset( 300 ) ); + BOOST_REQUIRE( op_result.fees.valid() && 1U == op_result.fees->size() ); + BOOST_CHECK( *op_result.fees->begin() == asset( 0 ) ); + + auto check_result = [&] + { + BOOST_CHECK( !pm_id(db).bitasset_data(db).is_individually_settled_to_fund() ); + BOOST_CHECK( pm_id(db).bitasset_data(db).is_globally_settled() ); + BOOST_CHECK_EQUAL( pm_id(db).bitasset_data(db).settlement_fund.value, 700 ); + + BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).accumulated_collateral_fees.value, 0 ); + + BOOST_CHECK_EQUAL( get_balance( alice_id, pm_id ), 700 ); + BOOST_CHECK_EQUAL( get_balance( alice_id, asset_id_type() ), init_balance - 700 ); + }; + + check_result(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + check_result(); + + // reset + db.pop_block(); + + } // for i + +} FC_LOG_AND_RETHROW() } + /** * Test case to reproduce https://github.com/bitshares/bitshares-core/issues/1883. * When there is only one fill_order object in the ticker rolling buffer, it should only be rolled out once. @@ -2081,7 +2384,7 @@ BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_gs ) BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100 BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_globally_settled() ); // generate a block generate_block(); @@ -2099,7 +2402,7 @@ BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_gs ) publish_feed( mpa_id, feeder_id, f ); // check - BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() ); BOOST_REQUIRE( db.find(settle_id) ); @@ -2112,7 +2415,7 @@ BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_gs ) generate_block(); // check - BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_globally_settled() ); // the settle order is cancelled BOOST_REQUIRE( !db.find(settle_id) ); diff --git a/tests/tests/swan_tests.cpp b/tests/tests/swan_tests.cpp index 4711f46e0d..9988dec23c 100644 --- a/tests/tests/swan_tests.cpp +++ b/tests/tests/swan_tests.cpp @@ -113,7 +113,7 @@ struct swan_fixture : database_fixture { FC_ASSERT( get_balance(borrower2(), back()) == init_balance - (2*amount2*denom+mssr-1)/mssr); } - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); return oid; } @@ -238,7 +238,7 @@ BOOST_AUTO_TEST_CASE( black_swan_issue_346 ) { const asset_object& bitusd = create_bitasset("USDBIT"+fc::to_string(trial)+"X", feeder_id); update_feed_producers( bitusd, {feeder.get_id()} ); - BOOST_CHECK( !bitusd.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !bitusd.bitasset_data(db).is_globally_settled() ); trial++; return bitusd; }; @@ -286,9 +286,9 @@ BOOST_AUTO_TEST_CASE( black_swan_issue_346 ) transfer( borrower, settler, bitusd.amount(100) ); // drop to $0.02 and settle - BOOST_CHECK( !bitusd.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !bitusd.bitasset_data(db).is_globally_settled() ); set_price( bitusd, bitusd.amount(1) / core.amount(50) ); // $0.02 - BOOST_CHECK( bitusd.bitasset_data(db).has_settlement() ); + BOOST_CHECK( bitusd.bitasset_data(db).is_globally_settled() ); GRAPHENE_REQUIRE_THROW( borrow( borrower2, bitusd.amount(100), asset(10000) ), fc::exception ); force_settle( settler, bitusd.amount(100) ); @@ -314,7 +314,7 @@ BOOST_AUTO_TEST_CASE( black_swan_issue_346 ) // We attempt to match against $0.019 order and black swan, // and this is intended behavior. See discussion in ticket. // - BOOST_CHECK( bitusd.bitasset_data(db).has_settlement() ); + BOOST_CHECK( bitusd.bitasset_data(db).is_globally_settled() ); BOOST_CHECK( db.find( oid_019 ) != nullptr ); BOOST_CHECK( db.find( oid_020 ) == nullptr ); } @@ -340,9 +340,9 @@ BOOST_AUTO_TEST_CASE( revive_recovered ) // revive after price recovers set_feed( 700, 800 ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); set_feed( 701, 800 ); - BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() ); graphene::app::database_api db_api( db, &( app.get_options() )); auto swan_symbol = _swan(db).symbol; @@ -375,11 +375,11 @@ BOOST_AUTO_TEST_CASE( revive_recovered_with_bids ) // price not good enough for recovery set_feed( 700, 800 ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); bid_collateral( borrower(), back().amount(10510), swan().amount(700) ); bid_collateral( borrower2(), back().amount(21000), swan().amount(1399) ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); graphene::app::database_api db_api( db, &( app.get_options() )); auto swan_symbol = _swan(db).symbol; @@ -388,7 +388,7 @@ BOOST_AUTO_TEST_CASE( revive_recovered_with_bids ) // revive after price recovers set_feed( 701, 800 ); - BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() ); bids = db_api.get_collateral_bids(swan_symbol, 100, 0); BOOST_CHECK( bids.empty() ); @@ -420,18 +420,18 @@ BOOST_AUTO_TEST_CASE( revive_recovered_with_bids_not_by_icr_before_hf_core_2290 BOOST_CHECK( swan().dynamic_data(db).current_supply == 1400 ); BOOST_CHECK( swan().bitasset_data(db).settlement_fund == 2800 ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); BOOST_CHECK( swan().bitasset_data(db).current_feed.settlement_price.is_null() ); BOOST_REQUIRE( HARDFORK_BSIP_77_PASSED( db.head_block_time() ) ); // price not good enough for recovery set_feed( 700, 800, 1750, 1800 ); // MCR = 1750, ICR = 1800 - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); bid_collateral( borrower(), back().amount(10510), swan().amount(700) ); bid_collateral( borrower2(), back().amount(21000), swan().amount(1399) ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); graphene::app::database_api db_api( db, &( app.get_options() )); auto swan_symbol = _swan(db).symbol; @@ -440,7 +440,7 @@ BOOST_AUTO_TEST_CASE( revive_recovered_with_bids_not_by_icr_before_hf_core_2290 // good feed price set_feed( 701, 800, 1750, 1800 ); // MCR = 1750, ICR = 1800 - BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() ); bids = db_api.get_collateral_bids(swan_symbol, 100, 0); BOOST_CHECK( bids.empty() ); @@ -473,16 +473,16 @@ BOOST_AUTO_TEST_CASE( revive_recovered_with_bids_by_icr_after_hf_core_2290 ) BOOST_CHECK( swan().dynamic_data(db).current_supply == 1400 ); BOOST_CHECK( swan().bitasset_data(db).settlement_fund == 2800 ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); BOOST_CHECK( swan().bitasset_data(db).current_feed.settlement_price.is_null() ); // price not good enough for recovery set_feed( 700, 800, 1750, 1800 ); // MCR = 1750, ICR = 1800 - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); bid_collateral( borrower(), back().amount(10510), swan().amount(700) ); bid_collateral( borrower2(), back().amount(21000), swan().amount(1399) ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); graphene::app::database_api db_api( db, &( app.get_options() )); auto swan_symbol = _swan(db).symbol; @@ -491,19 +491,19 @@ BOOST_AUTO_TEST_CASE( revive_recovered_with_bids_by_icr_after_hf_core_2290 ) // price still not good enough for recovery set_feed( 701, 800, 1750, 1800 ); // MCR = 1750, ICR = 1800 - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); bids = db_api.get_collateral_bids(swan_symbol, 100, 0); BOOST_CHECK_EQUAL( 2u, bids.size() ); // price still not good enough for recovery set_feed( 720, 800, 1750, 1800 ); // MCR = 1750, ICR = 1800 - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); bids = db_api.get_collateral_bids(swan_symbol, 100, 0); BOOST_CHECK_EQUAL( 2u, bids.size() ); // good feed price set_feed( 721, 800, 1750, 1800 ); // MCR = 1750, ICR = 1800 - BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() ); bids = db_api.get_collateral_bids(swan_symbol, 100, 0); BOOST_CHECK( bids.empty() ); @@ -570,26 +570,26 @@ BOOST_AUTO_TEST_CASE( recollateralize ) BOOST_CHECK( swan().dynamic_data(db).current_supply == 1400 ); BOOST_CHECK( swan().bitasset_data(db).settlement_fund == 2800 ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); BOOST_CHECK( swan().bitasset_data(db).current_feed.settlement_price.is_null() ); // doesn't happen without price feed bid_collateral( borrower(), back().amount(1400), swan().amount(700) ); bid_collateral( borrower2(), back().amount(1400), swan().amount(700) ); wait_for_maintenance(); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); set_feed(1, 2); // doesn't happen if cover is insufficient bid_collateral( borrower2(), back().amount(1400), swan().amount(600) ); wait_for_maintenance(); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); set_feed(1, 2); // doesn't happen if some bids have a bad swan price bid_collateral( borrower2(), back().amount(1050), swan().amount(700) ); wait_for_maintenance(); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); set_feed(1, 2); // works @@ -611,10 +611,10 @@ BOOST_AUTO_TEST_CASE( recollateralize ) FC_ASSERT( _borrower == bids[0].bidder ); FC_ASSERT( _borrower2 == bids[1].bidder ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); // revive wait_for_maintenance(); - BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() ); bids = db_api.get_collateral_bids(swan_symbol, 100, 0); BOOST_CHECK( bids.empty() ); } catch( const fc::exception& e) { @@ -637,7 +637,7 @@ BOOST_AUTO_TEST_CASE( recollateralize_not_by_icr_before_hf_core_2290 ) BOOST_CHECK( swan().dynamic_data(db).current_supply == 1400 ); BOOST_CHECK( swan().bitasset_data(db).settlement_fund == 2800 ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); BOOST_CHECK( swan().bitasset_data(db).current_feed.settlement_price.is_null() ); BOOST_REQUIRE( HARDFORK_BSIP_77_PASSED( db.head_block_time() ) ); @@ -654,7 +654,7 @@ BOOST_AUTO_TEST_CASE( recollateralize_not_by_icr_before_hf_core_2290 ) // revive wait_for_maintenance(); - BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() ); bids = db_api.get_collateral_bids(swan_symbol, 100, 0); BOOST_CHECK( bids.empty() ); @@ -680,7 +680,7 @@ BOOST_AUTO_TEST_CASE( recollateralize_by_icr_after_hf_core_2290 ) BOOST_CHECK( swan().dynamic_data(db).current_supply == 1400 ); BOOST_CHECK( swan().bitasset_data(db).settlement_fund == 2800 ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); BOOST_CHECK( swan().bitasset_data(db).current_feed.settlement_price.is_null() ); set_feed(1, 2, 1750, 1800); // MCR = 1750, ICR = 1800 @@ -688,20 +688,20 @@ BOOST_AUTO_TEST_CASE( recollateralize_by_icr_after_hf_core_2290 ) bid_collateral( borrower(), back().amount(1051), swan().amount(700) ); bid_collateral( borrower2(), back().amount(2100), swan().amount(1399) ); wait_for_maintenance(); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); set_feed(1, 2, 1750, 1800); // MCR = 1750, ICR = 1800 // doesn't happen if some bids have a bad swan price bid_collateral( borrower(), back().amount(1120), swan().amount(700) ); bid_collateral( borrower2(), back().amount(1122), swan().amount(700) ); wait_for_maintenance(); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); set_feed(1, 2, 1750, 1800); // MCR = 1750, ICR = 1800 // works bid_collateral( borrower(), back().amount(1121), swan().amount(700) ); bid_collateral( borrower2(), back().amount(1122), swan().amount(700) ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); graphene::app::database_api db_api( db, &( app.get_options() )); auto swan_symbol = _swan(db).symbol; @@ -710,7 +710,7 @@ BOOST_AUTO_TEST_CASE( recollateralize_by_icr_after_hf_core_2290 ) // revive wait_for_maintenance(); - BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() ); bids = db_api.get_collateral_bids(swan_symbol, 100, 0); BOOST_CHECK( bids.empty() ); @@ -764,11 +764,11 @@ BOOST_AUTO_TEST_CASE( revive_empty_recovered ) force_settle( borrower2(), swan().amount(1000) ); BOOST_CHECK_EQUAL( 0, swan().dynamic_data(db).current_supply.value ); BOOST_CHECK_EQUAL( 0, swan().bitasset_data(db).settlement_fund.value ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); // revive after price recovers set_feed( 1, 1 ); - BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() ); auto& call_idx = db.get_index_type().indices().get(); auto itr = call_idx.find( boost::make_tuple(_feedproducer, _swan) ); @@ -797,11 +797,11 @@ BOOST_AUTO_TEST_CASE( revive_empty ) force_settle( borrower2(), swan().amount(1000) ); BOOST_CHECK_EQUAL( 0, swan().dynamic_data(db).current_supply.value ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); // revive wait_for_maintenance(); - BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() ); } catch( const fc::exception& e) { edump((e.to_detail_string())); throw; @@ -829,7 +829,7 @@ BOOST_AUTO_TEST_CASE( revive_empty_with_bid ) set_feed( 1, 2 ); // this sell order is designed to trigger a black swan limit_order_id_type oid = create_sell_order( borrower2(), swan().amount(1), back().amount(3) )->get_id(); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); cancel_limit_order( oid(db) ); force_settle( borrower(), swan().amount(500) ); @@ -841,11 +841,11 @@ BOOST_AUTO_TEST_CASE( revive_empty_with_bid ) bid_collateral( borrower(), back().amount(3000), swan().amount(700) ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); // revive wait_for_maintenance(); - BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() ); graphene::app::database_api db_api( db, &( app.get_options() )); auto swan_symbol = _swan(db).symbol; vector bids = db_api.get_collateral_bids(swan_symbol, 100, 0); @@ -985,7 +985,7 @@ BOOST_AUTO_TEST_CASE( overflow ) BOOST_REQUIRE( itr != call_idx.end() ); BOOST_CHECK_EQUAL( 1399, itr->debt.value ); - BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( !swan().bitasset_data(db).is_globally_settled() ); } FC_LOG_AND_RETHROW() } /// Tests what kind of assets can have the disable_collateral_bidding flag / issuer permission @@ -1313,7 +1313,7 @@ BOOST_AUTO_TEST_CASE( disable_collateral_bidding_test ) bids = db_api.get_collateral_bids(swan_symbol, 100, 0); BOOST_CHECK_EQUAL( bids.size(), 0u ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); // Unable to bid BOOST_CHECK_THROW( bid_collateral( borrower(), back().amount(3000), swan().amount(700) ), fc::exception ); @@ -1335,7 +1335,7 @@ BOOST_AUTO_TEST_CASE( disable_collateral_bidding_test ) generate_block(); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); } FC_LOG_AND_RETHROW() } @@ -1357,7 +1357,7 @@ BOOST_AUTO_TEST_CASE( disable_collateral_bidding_cross_hardfork_test ) vector bids = db_api.get_collateral_bids(swan_symbol, 100, 0); BOOST_CHECK_EQUAL( bids.size(), 2u ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); // Advance to core-2281 hard fork auto mi = db.get_global_properties().parameters.maintenance_interval; @@ -1371,14 +1371,14 @@ BOOST_AUTO_TEST_CASE( disable_collateral_bidding_cross_hardfork_test ) bids = db_api.get_collateral_bids(swan_symbol, 100, 0); BOOST_CHECK_EQUAL( bids.size(), 0u ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); // Unable to bid BOOST_CHECK_THROW( bid_collateral( borrower(), back().amount(3000), swan().amount(700) ), fc::exception ); generate_block(); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); } FC_LOG_AND_RETHROW() } @@ -1413,18 +1413,18 @@ BOOST_AUTO_TEST_CASE( update_bitasset_after_gs ) set_expiration( db, trx ); BOOST_CHECK( swan().bitasset_data(db).options.feed_lifetime_sec == old_options.feed_lifetime_sec ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); // should succeed PUSH_TX(db, trx, ~0); BOOST_CHECK( swan().bitasset_data(db).options.feed_lifetime_sec == old_options.feed_lifetime_sec + 1 ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); generate_block(); BOOST_CHECK( swan().bitasset_data(db).options.feed_lifetime_sec == old_options.feed_lifetime_sec + 1 ); - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); // unable to update backing asset @@ -1456,7 +1456,7 @@ BOOST_AUTO_TEST_CASE( update_bitasset_after_gs ) const auto& check_result = [&]() { - BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).is_globally_settled() ); BOOST_CHECK( swan().bitasset_data(db).options.feed_lifetime_sec == old_options.feed_lifetime_sec + 1 );